summaryrefslogtreecommitdiffstats
path: root/lib/services
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
commite5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch)
treea6716c9275b4b413f6c9194798b34b91affb3cc7 /lib/services
parentInitial commit. (diff)
downloadpacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz
pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/services')
-rw-r--r--lib/services/Makefile.am43
-rw-r--r--lib/services/dbus.c776
-rw-r--r--lib/services/pcmk-dbus.h45
-rw-r--r--lib/services/services.c1417
-rw-r--r--lib/services/services_linux.c1438
-rw-r--r--lib/services/services_lsb.c341
-rw-r--r--lib/services/services_lsb.h21
-rw-r--r--lib/services/services_nagios.c220
-rw-r--r--lib/services/services_nagios.h28
-rw-r--r--lib/services/services_ocf.c179
-rw-r--r--lib/services/services_ocf.h31
-rw-r--r--lib/services/services_private.h101
-rw-r--r--lib/services/systemd.c1100
-rw-r--r--lib/services/systemd.h30
-rw-r--r--lib/services/upstart.c701
-rw-r--r--lib/services/upstart.h31
16 files changed, 6502 insertions, 0 deletions
diff --git a/lib/services/Makefile.am b/lib/services/Makefile.am
new file mode 100644
index 0000000..a7e10c9
--- /dev/null
+++ b/lib/services/Makefile.am
@@ -0,0 +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 31:2:3
+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/dbus.c b/lib/services/dbus.c
new file mode 100644
index 0000000..f052c0a
--- /dev/null
+++ b/lib/services/dbus.c
@@ -0,0 +1,776 @@
+/*
+ * Copyright 2014-2022 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 <crm/crm.h>
+#include <dbus/dbus.h>
+#include <pcmk-dbus.h>
+
+/*
+ * DBus message dispatch
+ */
+
+// List of DBus connections (DBusConnection*) with messages available
+static GList *conn_dispatches = NULL;
+
+/*!
+ * \internal
+ * \brief Save an indication that DBus messages need dispatching
+ *
+ * \param[in] connection DBus connection with messages to dispatch
+ * \param[in] new_status Dispatch status as reported by DBus library
+ * \param[in] data Ignored
+ *
+ * \note This is suitable to be used as a DBus status dispatch function.
+ * As mentioned in the DBus documentation, dbus_connection_dispatch() must
+ * not be called from within this function, and any re-entrancy is a bad
+ * idea. Instead, this should just flag the main loop that messages need
+ * to be dispatched.
+ */
+static void
+update_dispatch_status(DBusConnection *connection,
+ DBusDispatchStatus new_status, void *data)
+{
+ if (new_status == DBUS_DISPATCH_DATA_REMAINS) {
+ crm_trace("DBus connection has messages available for dispatch");
+ conn_dispatches = g_list_prepend(conn_dispatches, connection);
+ } else {
+ crm_trace("DBus connection has no messages available for dispatch "
+ "(status %d)", new_status);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Dispatch available messages on all DBus connections
+ */
+static void
+dispatch_messages(void)
+{
+ for (GList *gIter = conn_dispatches; gIter != NULL; gIter = gIter->next) {
+ DBusConnection *connection = gIter->data;
+
+ while (dbus_connection_get_dispatch_status(connection)
+ == DBUS_DISPATCH_DATA_REMAINS) {
+ crm_trace("Dispatching available messages on DBus connection");
+ dbus_connection_dispatch(connection);
+ }
+ }
+ g_list_free(conn_dispatches);
+ conn_dispatches = NULL;
+}
+
+
+/*
+ * DBus file descriptor watches
+ *
+ * The DBus library allows the caller to register functions for the library to
+ * use for file descriptor notifications via a main loop.
+ */
+
+/* Copied from dbus-watch.c */
+static const char*
+dbus_watch_flags_to_string(int flags)
+{
+ const char *watch_type;
+
+ if ((flags & DBUS_WATCH_READABLE) && (flags & DBUS_WATCH_WRITABLE)) {
+ watch_type = "read/write";
+ } else if (flags & DBUS_WATCH_READABLE) {
+ watch_type = "read";
+ } else if (flags & DBUS_WATCH_WRITABLE) {
+ watch_type = "write";
+ } else {
+ watch_type = "neither read nor write";
+ }
+ return watch_type;
+}
+
+/*!
+ * \internal
+ * \brief Dispatch data available on a DBus file descriptor watch
+ *
+ * \param[in,out] userdata Pointer to the DBus watch
+ *
+ * \return Always 0
+ * \note This is suitable for use as a dispatch function in
+ * struct mainloop_fd_callbacks (which means that a negative return value
+ * would indicate the file descriptor is no longer required).
+ */
+static int
+dispatch_fd_data(gpointer userdata)
+{
+ bool oom = FALSE;
+ DBusWatch *watch = userdata;
+ int flags = dbus_watch_get_flags(watch);
+ bool enabled = dbus_watch_get_enabled (watch);
+
+ crm_trace("Dispatching DBus watch for file descriptor %d "
+ "with flags %#x (%s)",
+ dbus_watch_get_unix_fd(watch), flags,
+ dbus_watch_flags_to_string(flags));
+
+ if (enabled && (flags & (DBUS_WATCH_READABLE|DBUS_WATCH_WRITABLE))) {
+ oom = !dbus_watch_handle(watch, flags);
+
+ } else if (enabled) {
+ oom = !dbus_watch_handle(watch, DBUS_WATCH_ERROR);
+ }
+
+ if (flags != dbus_watch_get_flags(watch)) {
+ flags = dbus_watch_get_flags(watch);
+ crm_trace("Dispatched DBus file descriptor watch: now %#x (%s)",
+ flags, dbus_watch_flags_to_string(flags));
+ }
+
+ if (oom) {
+ crm_crit("Could not dispatch DBus file descriptor data: Out of memory");
+ } else {
+ dispatch_messages();
+ }
+ return 0;
+}
+
+static void
+watch_fd_closed(gpointer userdata)
+{
+ crm_trace("DBus watch for file descriptor %d is now closed",
+ dbus_watch_get_unix_fd((DBusWatch *) userdata));
+}
+
+static struct mainloop_fd_callbacks pcmk_dbus_cb = {
+ .dispatch = dispatch_fd_data,
+ .destroy = watch_fd_closed,
+};
+
+static dbus_bool_t
+add_dbus_watch(DBusWatch *watch, void *data)
+{
+ int fd = dbus_watch_get_unix_fd(watch);
+
+ mainloop_io_t *client = mainloop_add_fd("dbus", G_PRIORITY_DEFAULT, fd,
+ watch, &pcmk_dbus_cb);
+
+ crm_trace("Added DBus watch for file descriptor %d", fd);
+ dbus_watch_set_data(watch, client, NULL);
+ return TRUE;
+}
+
+static void
+toggle_dbus_watch(DBusWatch *watch, void *data)
+{
+ // @TODO Should this do something more?
+ crm_debug("DBus watch for file descriptor %d is now %s",
+ dbus_watch_get_unix_fd(watch),
+ (dbus_watch_get_enabled(watch)? "enabled" : "disabled"));
+}
+
+static void
+remove_dbus_watch(DBusWatch *watch, void *data)
+{
+ crm_trace("Removed DBus watch for file descriptor %d",
+ dbus_watch_get_unix_fd(watch));
+ mainloop_del_fd((mainloop_io_t *) dbus_watch_get_data(watch));
+}
+
+static void
+register_watch_functions(DBusConnection *connection)
+{
+ dbus_connection_set_watch_functions(connection, add_dbus_watch,
+ remove_dbus_watch,
+ toggle_dbus_watch, NULL, NULL);
+}
+
+/*
+ * DBus main loop timeouts
+ *
+ * The DBus library allows the caller to register functions for the library to
+ * use for managing timers via a main loop.
+ */
+
+static gboolean
+timer_popped(gpointer data)
+{
+ crm_debug("%dms DBus timer expired",
+ dbus_timeout_get_interval((DBusTimeout *) data));
+ dbus_timeout_handle(data);
+ return FALSE;
+}
+
+static dbus_bool_t
+add_dbus_timer(DBusTimeout *timeout, void *data)
+{
+ int interval_ms = dbus_timeout_get_interval(timeout);
+ guint id = g_timeout_add(interval_ms, timer_popped, timeout);
+
+ if (id) {
+ dbus_timeout_set_data(timeout, GUINT_TO_POINTER(id), NULL);
+ }
+ crm_trace("Added %dms DBus timer", interval_ms);
+ return TRUE;
+}
+
+static void
+remove_dbus_timer(DBusTimeout *timeout, void *data)
+{
+ void *vid = dbus_timeout_get_data(timeout);
+ guint id = GPOINTER_TO_UINT(vid);
+
+ crm_trace("Removing %dms DBus timer", dbus_timeout_get_interval(timeout));
+ if (id) {
+ g_source_remove(id);
+ dbus_timeout_set_data(timeout, 0, NULL);
+ }
+}
+
+static void
+toggle_dbus_timer(DBusTimeout *timeout, void *data)
+{
+ bool enabled = dbus_timeout_get_enabled(timeout);
+
+ crm_trace("Toggling %dms DBus timer %s",
+ dbus_timeout_get_interval(timeout), (enabled? "off": "on"));
+ if (enabled) {
+ add_dbus_timer(timeout, data);
+ } else {
+ remove_dbus_timer(timeout, data);
+ }
+}
+
+static void
+register_timer_functions(DBusConnection *connection)
+{
+ dbus_connection_set_timeout_functions(connection, add_dbus_timer,
+ remove_dbus_timer,
+ toggle_dbus_timer, NULL, NULL);
+}
+
+/*
+ * General DBus utilities
+ */
+
+DBusConnection *
+pcmk_dbus_connect(void)
+{
+ DBusError err;
+ DBusConnection *connection;
+
+ dbus_error_init(&err);
+ connection = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
+ if (dbus_error_is_set(&err)) {
+ crm_err("Could not connect to DBus: %s", err.message);
+ dbus_error_free(&err);
+ return NULL;
+ }
+ if (connection == NULL) {
+ return NULL;
+ }
+
+ /* Tell libdbus not to exit the process when a disconnect happens. This
+ * defaults to FALSE but is toggled on by the dbus_bus_get() call above.
+ */
+ dbus_connection_set_exit_on_disconnect(connection, FALSE);
+
+ // Set custom handlers for various situations
+ register_timer_functions(connection);
+ register_watch_functions(connection);
+ dbus_connection_set_dispatch_status_function(connection,
+ update_dispatch_status,
+ NULL, NULL);
+
+ // Call the dispatch function to check for any messages waiting already
+ update_dispatch_status(connection,
+ dbus_connection_get_dispatch_status(connection),
+ NULL);
+ return connection;
+}
+
+void
+pcmk_dbus_disconnect(DBusConnection *connection)
+{
+ /* Per the DBus documentation, connections created with
+ * dbus_connection_open() are owned by libdbus and should never be closed.
+ *
+ * @TODO Should we call dbus_connection_unref() here?
+ */
+ return;
+}
+
+// Custom DBus error names to use
+#define ERR_NO_REQUEST "org.clusterlabs.pacemaker.NoRequest"
+#define ERR_NO_REPLY "org.clusterlabs.pacemaker.NoReply"
+#define ERR_INVALID_REPLY "org.clusterlabs.pacemaker.InvalidReply"
+#define ERR_INVALID_REPLY_METHOD "org.clusterlabs.pacemaker.InvalidReply.Method"
+#define ERR_INVALID_REPLY_SIGNAL "org.clusterlabs.pacemaker.InvalidReply.Signal"
+#define ERR_INVALID_REPLY_TYPE "org.clusterlabs.pacemaker.InvalidReply.Type"
+#define ERR_SEND_FAILED "org.clusterlabs.pacemaker.SendFailed"
+
+/*!
+ * \internal
+ * \brief Check whether a DBus reply indicates an error occurred
+ *
+ * \param[in] pending If non-NULL, indicates that a DBus request was sent
+ * \param[in] reply Reply received from DBus
+ * \param[out] ret If non-NULL, will be set to DBus error, if any
+ *
+ * \return TRUE if an error was found, FALSE otherwise
+ *
+ * \note Following the DBus API convention, a TRUE return is exactly equivalent
+ * to ret being set. If ret is provided and this function returns TRUE,
+ * the caller is responsible for calling dbus_error_free() on ret when
+ * done using it.
+ */
+bool
+pcmk_dbus_find_error(const DBusPendingCall *pending, DBusMessage *reply,
+ DBusError *ret)
+{
+ DBusError error;
+
+ dbus_error_init(&error);
+
+ if (pending == NULL) {
+ dbus_set_error_const(&error, ERR_NO_REQUEST, "No request sent");
+
+ } else if (reply == NULL) {
+ dbus_set_error_const(&error, ERR_NO_REPLY, "No reply");
+
+ } else {
+ DBusMessageIter args;
+ int dtype = dbus_message_get_type(reply);
+
+ switch (dtype) {
+ case DBUS_MESSAGE_TYPE_METHOD_RETURN:
+ {
+ char *sig = NULL;
+
+ dbus_message_iter_init(reply, &args);
+ crm_trace("Received DBus reply with argument type '%s'",
+ (sig = dbus_message_iter_get_signature(&args)));
+ if (sig != NULL) {
+ dbus_free(sig);
+ }
+ }
+ break;
+ case DBUS_MESSAGE_TYPE_INVALID:
+ dbus_set_error_const(&error, ERR_INVALID_REPLY,
+ "Invalid reply");
+ break;
+ case DBUS_MESSAGE_TYPE_METHOD_CALL:
+ dbus_set_error_const(&error, ERR_INVALID_REPLY_METHOD,
+ "Invalid reply (method call)");
+ break;
+ case DBUS_MESSAGE_TYPE_SIGNAL:
+ dbus_set_error_const(&error, ERR_INVALID_REPLY_SIGNAL,
+ "Invalid reply (signal)");
+ break;
+ case DBUS_MESSAGE_TYPE_ERROR:
+ dbus_set_error_from_message(&error, reply);
+ break;
+ default:
+ dbus_set_error(&error, ERR_INVALID_REPLY_TYPE,
+ "Unknown reply type %d", dtype);
+ }
+ }
+
+ if (dbus_error_is_set(&error)) {
+ crm_trace("DBus reply indicated error '%s' (%s)",
+ error.name, error.message);
+ if (ret) {
+ dbus_error_init(ret);
+ dbus_move_error(&error, ret);
+ } else {
+ dbus_error_free(&error);
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*!
+ * \internal
+ * \brief Send a DBus request and wait for the reply
+ *
+ * \param[in,out] msg DBus request to send
+ * \param[in,out] connection DBus connection to use
+ * \param[out] error If non-NULL, will be set to error, if any
+ * \param[in] timeout Timeout to use for request
+ *
+ * \return DBus reply
+ *
+ * \note If error is non-NULL, it is initialized, so the caller may always use
+ * dbus_error_is_set() to determine whether an error occurred; the caller
+ * is responsible for calling dbus_error_free() in this case.
+ */
+DBusMessage *
+pcmk_dbus_send_recv(DBusMessage *msg, DBusConnection *connection,
+ DBusError *error, int timeout)
+{
+ const char *method = NULL;
+ DBusMessage *reply = NULL;
+ DBusPendingCall* pending = NULL;
+
+ CRM_ASSERT(dbus_message_get_type (msg) == DBUS_MESSAGE_TYPE_METHOD_CALL);
+ method = dbus_message_get_member (msg);
+
+ /* Ensure caller can reliably check whether error is set */
+ if (error) {
+ dbus_error_init(error);
+ }
+
+ if (timeout <= 0) {
+ /* DBUS_TIMEOUT_USE_DEFAULT (-1) tells DBus to use a sane default */
+ timeout = DBUS_TIMEOUT_USE_DEFAULT;
+ }
+
+ // send message and get a handle for a reply
+ if (!dbus_connection_send_with_reply(connection, msg, &pending, timeout)) {
+ if (error) {
+ dbus_set_error(error, ERR_SEND_FAILED,
+ "Could not queue DBus '%s' request", method);
+ }
+ return NULL;
+ }
+
+ dbus_connection_flush(connection);
+
+ if (pending) {
+ /* block until we receive a reply */
+ dbus_pending_call_block(pending);
+
+ /* get the reply message */
+ reply = dbus_pending_call_steal_reply(pending);
+ }
+
+ (void) pcmk_dbus_find_error(pending, reply, error);
+
+ if (pending) {
+ /* free the pending message handle */
+ dbus_pending_call_unref(pending);
+ }
+
+ return reply;
+}
+
+/*!
+ * \internal
+ * \brief Send a DBus message with a callback for the reply
+ *
+ * \param[in,out] msg DBus message to send
+ * \param[in,out] connection DBus connection to send on
+ * \param[in] done Function to call when pending call completes
+ * \param[in] user_data Data to pass to done callback
+ *
+ * \return Handle for reply on success, NULL on error
+ * \note The caller can assume that the done callback is called always and
+ * only when the return value is non-NULL. (This allows the caller to
+ * know where it should free dynamically allocated user_data.)
+ */
+DBusPendingCall *
+pcmk_dbus_send(DBusMessage *msg, DBusConnection *connection,
+ void (*done)(DBusPendingCall *pending, void *user_data),
+ void *user_data, int timeout)
+{
+ const char *method = NULL;
+ DBusPendingCall* pending = NULL;
+
+ CRM_ASSERT(done);
+ CRM_ASSERT(dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_CALL);
+ method = dbus_message_get_member(msg);
+
+ if (timeout <= 0) {
+ /* DBUS_TIMEOUT_USE_DEFAULT (-1) tells DBus to use a sane default */
+ timeout = DBUS_TIMEOUT_USE_DEFAULT;
+ }
+
+ // send message and get a handle for a reply
+ if (!dbus_connection_send_with_reply(connection, msg, &pending, timeout)) {
+ crm_err("Could not send DBus %s message: failed", method);
+ return NULL;
+
+ } else if (pending == NULL) {
+ crm_err("Could not send DBus %s message: connection may be closed",
+ method);
+ return NULL;
+ }
+
+ if (dbus_pending_call_get_completed(pending)) {
+ crm_info("DBus %s message completed too soon", method);
+ /* Calling done() directly in this case instead of setting notify below
+ * breaks things
+ */
+ }
+ if (!dbus_pending_call_set_notify(pending, done, user_data, NULL)) {
+ return NULL;
+ }
+ return pending;
+}
+
+bool
+pcmk_dbus_type_check(DBusMessage *msg, DBusMessageIter *field, int expected,
+ const char *function, int line)
+{
+ int dtype = 0;
+ DBusMessageIter lfield;
+
+ if (field == NULL) {
+ if (dbus_message_iter_init(msg, &lfield)) {
+ field = &lfield;
+ }
+ }
+
+ if (field == NULL) {
+ do_crm_log_alias(LOG_INFO, __FILE__, function, line,
+ "DBus reply has empty parameter list (expected '%c')",
+ expected);
+ return FALSE;
+ }
+
+ dtype = dbus_message_iter_get_arg_type(field);
+
+ if (dtype != expected) {
+ DBusMessageIter args;
+ char *sig;
+
+ dbus_message_iter_init(msg, &args);
+ sig = dbus_message_iter_get_signature(&args);
+ do_crm_log_alias(LOG_INFO, __FILE__, function, line,
+ "DBus reply has unexpected type "
+ "(expected '%c' not '%c' in '%s')",
+ expected, dtype, sig);
+ dbus_free(sig);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/*
+ * Property queries
+ */
+
+/* DBus APIs often provide queryable properties that use this standard
+ * interface. See:
+ * https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-properties
+ */
+#define BUS_PROPERTY_IFACE "org.freedesktop.DBus.Properties"
+
+// Callback prototype for when a DBus property query result is received
+typedef void (*property_callback_func)(const char *name, // Property name
+ const char *value, // Property value
+ void *userdata); // Caller-provided data
+
+// Data needed by DBus property queries
+struct property_query {
+ char *name; // Property name being queried
+ char *target; // Name of DBus bus that query should be sent to
+ char *object; // DBus object path for object with the property
+ void *userdata; // Caller-provided data to supply to callback
+ property_callback_func callback; // Function to call when result is received
+};
+
+static void
+free_property_query(struct property_query *data)
+{
+ free(data->target);
+ free(data->object);
+ free(data->name);
+ free(data);
+}
+
+static char *
+handle_query_result(DBusMessage *reply, struct property_query *data)
+{
+ DBusError error;
+ char *output = NULL;
+ DBusMessageIter args;
+ DBusMessageIter variant_iter;
+ DBusBasicValue value;
+
+ // First, check if the reply contains an error
+ if (pcmk_dbus_find_error((void*)&error, reply, &error)) {
+ crm_err("DBus query for %s property '%s' failed: %s",
+ data->object, data->name, error.message);
+ dbus_error_free(&error);
+ goto cleanup;
+ }
+
+ // The lone output argument should be a DBus variant type
+ dbus_message_iter_init(reply, &args);
+ if (!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_VARIANT,
+ __func__, __LINE__)) {
+ crm_err("DBus query for %s property '%s' failed: Unexpected reply type",
+ data->object, data->name);
+ goto cleanup;
+ }
+
+ // The variant should be a string
+ dbus_message_iter_recurse(&args, &variant_iter);
+ if (!pcmk_dbus_type_check(reply, &variant_iter, DBUS_TYPE_STRING,
+ __func__, __LINE__)) {
+ crm_err("DBus query for %s property '%s' failed: "
+ "Unexpected variant type", data->object, data->name);
+ goto cleanup;
+ }
+ dbus_message_iter_get_basic(&variant_iter, &value);
+
+ // There should be no more arguments (in variant or reply)
+ dbus_message_iter_next(&variant_iter);
+ if (dbus_message_iter_get_arg_type(&variant_iter) != DBUS_TYPE_INVALID) {
+ crm_err("DBus query for %s property '%s' failed: "
+ "Too many arguments in reply",
+ data->object, data->name);
+ goto cleanup;
+ }
+ dbus_message_iter_next(&args);
+ if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_INVALID) {
+ crm_err("DBus query for %s property '%s' failed: "
+ "Too many arguments in reply", data->object, data->name);
+ goto cleanup;
+ }
+
+ crm_trace("DBus query result for %s: %s='%s'",
+ data->object, data->name, (value.str? value.str : ""));
+
+ if (data->callback) { // Query was asynchronous
+ data->callback(data->name, (value.str? value.str : ""), data->userdata);
+
+ } else { // Query was synchronous
+ output = strdup(value.str? value.str : "");
+ }
+
+ cleanup:
+ free_property_query(data);
+ return output;
+}
+
+static void
+async_query_result_cb(DBusPendingCall *pending, void *user_data)
+{
+ DBusMessage *reply = NULL;
+ char *value = NULL;
+
+ if (pending) {
+ reply = dbus_pending_call_steal_reply(pending);
+ }
+
+ value = handle_query_result(reply, user_data);
+ free(value);
+
+ if (reply) {
+ dbus_message_unref(reply);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Query a property on a DBus object
+ *
+ * \param[in,out] connection An active connection to DBus
+ * \param[in] target DBus name that the query should be sent to
+ * \param[in] obj DBus object path for object with the property
+ * \param[in] iface DBus interface for property to query
+ * \param[in] name Name of property to query
+ * \param[in] callback If not NULL, perform query asynchronously and call
+ * this function when query completes
+ * \param[in,out] userdata Caller-provided data to provide to \p callback
+ * \param[out] pending If \p callback is not NULL, this will be set to
+ * handle for the reply (or NULL on error)
+ * \param[in] timeout Abort query if it takes longer than this (ms)
+ *
+ * \return NULL if \p callback is non-NULL (i.e. asynchronous), otherwise a
+ * newly allocated string with property value
+ * \note It is the caller's responsibility to free the result with free().
+ */
+char *
+pcmk_dbus_get_property(DBusConnection *connection, const char *target,
+ const char *obj, const gchar * iface, const char *name,
+ property_callback_func callback, void *userdata,
+ DBusPendingCall **pending, int timeout)
+{
+ DBusMessage *msg;
+ char *output = NULL;
+ struct property_query *query_data = NULL;
+
+ CRM_CHECK((connection != NULL) && (target != NULL) && (obj != NULL)
+ && (iface != NULL) && (name != NULL), return NULL);
+
+ crm_trace("Querying DBus %s for %s property '%s'",
+ target, obj, name);
+
+ // Create a new message to use to invoke method
+ msg = dbus_message_new_method_call(target, obj, BUS_PROPERTY_IFACE, "Get");
+ if (msg == NULL) {
+ crm_err("DBus query for %s property '%s' failed: "
+ "Unable to create message", obj, name);
+ return NULL;
+ }
+
+ // Add the interface name and property name as message arguments
+ if (!dbus_message_append_args(msg,
+ DBUS_TYPE_STRING, &iface,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID)) {
+ crm_err("DBus query for %s property '%s' failed: "
+ "Could not append arguments", obj, name);
+ dbus_message_unref(msg);
+ return NULL;
+ }
+
+ query_data = malloc(sizeof(struct property_query));
+ if (query_data == NULL) {
+ crm_crit("DBus query for %s property '%s' failed: Out of memory",
+ obj, name);
+ dbus_message_unref(msg);
+ return NULL;
+ }
+
+ query_data->target = strdup(target);
+ query_data->object = strdup(obj);
+ query_data->callback = callback;
+ query_data->userdata = userdata;
+ query_data->name = strdup(name);
+ CRM_CHECK((query_data->target != NULL)
+ && (query_data->object != NULL)
+ && (query_data->name != NULL),
+ free_property_query(query_data);
+ dbus_message_unref(msg);
+ return NULL);
+
+ if (query_data->callback) { // Asynchronous
+ DBusPendingCall *local_pending;
+
+ local_pending = pcmk_dbus_send(msg, connection, async_query_result_cb,
+ query_data, timeout);
+ if (local_pending == NULL) {
+ // async_query_result_cb() was not called in this case
+ free_property_query(query_data);
+ query_data = NULL;
+ }
+
+ if (pending) {
+ *pending = local_pending;
+ }
+
+ } else { // Synchronous
+ DBusMessage *reply = pcmk_dbus_send_recv(msg, connection, NULL,
+ timeout);
+
+ output = handle_query_result(reply, query_data);
+
+ if (reply) {
+ dbus_message_unref(reply);
+ }
+ }
+
+ dbus_message_unref(msg);
+
+ return output;
+}
diff --git a/lib/services/pcmk-dbus.h b/lib/services/pcmk-dbus.h
new file mode 100644
index 0000000..a9d0cbc
--- /dev/null
+++ b/lib/services/pcmk-dbus.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2014-2022 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#ifndef PCMK_DBUS__H
+# define PCMK_DBUS__H
+
+# include <dbus/dbus.h>
+
+# ifndef DBUS_TIMEOUT_USE_DEFAULT
+# define DBUS_TIMEOUT_USE_DEFAULT -1
+# endif
+
+G_GNUC_INTERNAL
+DBusConnection *pcmk_dbus_connect(void);
+
+G_GNUC_INTERNAL
+void pcmk_dbus_disconnect(DBusConnection *connection);
+
+G_GNUC_INTERNAL
+DBusPendingCall *pcmk_dbus_send(DBusMessage *msg, DBusConnection *connection,
+ void(*done)(DBusPendingCall *pending, void *user_data), void *user_data, int timeout);
+
+G_GNUC_INTERNAL
+DBusMessage *pcmk_dbus_send_recv(DBusMessage *msg, DBusConnection *connection, DBusError *error, int timeout);
+
+G_GNUC_INTERNAL
+bool pcmk_dbus_type_check(DBusMessage *msg, DBusMessageIter *field, int expected, const char *function, int line);
+
+G_GNUC_INTERNAL
+char *pcmk_dbus_get_property(
+ DBusConnection *connection, const char *target, const char *obj, const gchar * iface, const char *name,
+ void (*callback)(const char *name, const char *value, void *userdata), void *userdata,
+ DBusPendingCall **pending, int timeout);
+
+G_GNUC_INTERNAL
+bool pcmk_dbus_find_error(const DBusPendingCall *pending, DBusMessage *reply,
+ DBusError *error);
+
+#endif /* PCMK_DBUS__H */
diff --git a/lib/services/services.c b/lib/services/services.c
new file mode 100644
index 0000000..b60d8bd
--- /dev/null
+++ b/lib/services/services.c
@@ -0,0 +1,1417 @@
+/*
+ * Copyright 2010-2022 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(const 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);
+ int rc = pcmk_rc_ok;
+
+ 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) {
+ rc = services__ocf_prepare(op);
+
+ } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_LSB) == 0) {
+ rc = services__lsb_prepare(op);
+
+#if SUPPORT_SYSTEMD
+ } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_SYSTEMD) == 0) {
+ rc = services__systemd_prepare(op);
+#endif
+#if SUPPORT_UPSTART
+ } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_UPSTART) == 0) {
+ rc = services__upstart_prepare(op);
+#endif
+#if SUPPORT_NAGIOS
+ } else if (strcasecmp(op->standard, PCMK_RESOURCE_CLASS_NAGIOS) == 0) {
+ rc = services__nagios_prepare(op);
+#endif
+ } else {
+ crm_info("Unknown resource standard: %s", op->standard);
+ rc = ENOENT;
+ }
+
+ if (rc != pcmk_rc_ok) {
+ crm_info("Cannot prepare %s operation for %s: %s",
+ action, name, strerror(rc));
+ services__handle_exec_error(op, rc);
+ }
+ 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->id = strdup(id);
+ action->standard = strdup(PCMK_RESOURCE_CLASS_ALERT);
+ CRM_ASSERT((action->id != NULL) && (action->standard != NULL));
+
+ action->timeout = timeout;
+ 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] op 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,out] 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(). However, unless someone
+ * intentionally creates a recurring alert action, this will never return
+ * FALSE.
+ */
+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 HAVE_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 HAVE_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;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Map an actual resource action result to a standard OCF result
+ *
+ * \param[in] standard Agent standard (must not be "service")
+ * \param[in] action Action that result is for
+ * \param[in] exit_status Actual agent exit status
+ *
+ * \return Standard OCF result
+ */
+enum ocf_exitcode
+services_result2ocf(const char *standard, const char *action, int exit_status)
+{
+ if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_OCF, pcmk__str_casei)) {
+ return services__ocf2ocf(exit_status);
+
+#if SUPPORT_SYSTEMD
+ } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_SYSTEMD,
+ pcmk__str_casei)) {
+ return services__systemd2ocf(exit_status);
+#endif
+
+#if SUPPORT_UPSTART
+ } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_UPSTART,
+ pcmk__str_casei)) {
+ return services__upstart2ocf(exit_status);
+#endif
+
+#if SUPPORT_NAGIOS
+ } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_NAGIOS,
+ pcmk__str_casei)) {
+ return services__nagios2ocf(exit_status);
+#endif
+
+ } else if (pcmk__str_eq(standard, PCMK_RESOURCE_CLASS_LSB,
+ pcmk__str_casei)) {
+ return services__lsb2ocf(action, exit_status);
+
+ } else {
+ crm_warn("Treating result from unknown standard '%s' as OCF",
+ ((standard == NULL)? "unspecified" : standard));
+ return services__ocf2ocf(exit_status);
+ }
+}
+
+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 HAVE_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_cancelled(op);
+ 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,out] 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,out] 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's pending or because
+ * it failed to execute and was already freed)
+ *
+ * \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(const 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 *))
+{
+ CRM_CHECK(op != NULL, return TRUE);
+
+ op->synchronous = false;
+ if (action_callback != NULL) {
+ op->opaque->callback = action_callback;
+ }
+ if (action_fork_callback != NULL) {
+ op->opaque->fork_callback = action_fork_callback;
+ }
+
+ if (op->interval_ms > 0) {
+ init_recurring_actions();
+ if (handle_duplicate_recurring(op)) {
+ /* 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,out] 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_info("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_info("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_info("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 Set the result of an action, with a formatted exit reason
+ *
+ * \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] format printf-style format for a human-friendly
+ * description of reason for result
+ * \param[in] ... arguments for \p format
+ */
+void
+services__format_result(svc_action_t *action, int agent_status,
+ enum pcmk_exec_status exec_status,
+ const char *format, ...)
+{
+ va_list ap;
+ int len = 0;
+ char *reason = NULL;
+
+ if (action == NULL) {
+ return;
+ }
+
+ action->rc = agent_status;
+ action->status = exec_status;
+
+ if (format != NULL) {
+ va_start(ap, format);
+ len = vasprintf(&reason, format, ap);
+ CRM_ASSERT(len > 0);
+ va_end(ap);
+ }
+ free(action->opaque->exit_reason);
+ action->opaque->exit_reason = reason;
+}
+
+/*!
+ * \internal
+ * \brief Set the result of an action to cancelled
+ *
+ * \param[out] action Where to set action result
+ *
+ * \note This sets execution status but leaves the exit status unchanged
+ */
+void
+services__set_cancelled(svc_action_t *action)
+{
+ if (action != NULL) {
+ action->status = PCMK_EXEC_CANCELLED;
+ free(action->opaque->exit_reason);
+ action->opaque->exit_reason = NULL;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Get a readable description of what an action is for
+ *
+ * \param[in] action Action to check
+ *
+ * \return Readable name for the kind of \p action
+ */
+const char *
+services__action_kind(const svc_action_t *action)
+{
+ if ((action == NULL) || (action->standard == NULL)) {
+ return "Process";
+ } else if (pcmk__str_eq(action->standard, PCMK_RESOURCE_CLASS_STONITH,
+ pcmk__str_none)) {
+ return "Fence agent";
+ } else if (pcmk__str_eq(action->standard, PCMK_RESOURCE_CLASS_ALERT,
+ pcmk__str_none)) {
+ return "Alert agent";
+ } else {
+ return "Resource agent";
+ }
+}
+
+/*!
+ * \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(const svc_action_t *action)
+{
+ return action->opaque->exit_reason;
+}
+
+/*!
+ * \internal
+ * \brief Steal stdout from an action
+ *
+ * \param[in,out] action Action whose stdout is desired
+ *
+ * \return Action's stdout (which may be NULL)
+ * \note Upon return, \p action will no longer track the output, so it is the
+ * caller's responsibility to free the return value.
+ */
+char *
+services__grab_stdout(svc_action_t *action)
+{
+ char *output = action->stdout_data;
+
+ action->stdout_data = NULL;
+ return output;
+}
+
+/*!
+ * \internal
+ * \brief Steal stderr from an action
+ *
+ * \param[in,out] action Action whose stderr is desired
+ *
+ * \return Action's stderr (which may be NULL)
+ * \note Upon return, \p action will no longer track the output, so it is the
+ * caller's responsibility to free the return value.
+ */
+char *
+services__grab_stderr(svc_action_t *action)
+{
+ char *output = action->stderr_data;
+
+ action->stderr_data = NULL;
+ return output;
+}
diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c
new file mode 100644
index 0000000..fb12f73
--- /dev/null
+++ b/lib/services/services_linux.c
@@ -0,0 +1,1438 @@
+/*
+ * Copyright 2010-2022 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_info("Wait for child process completion failed: %s "
+ CRM_XS " source=sigprocmask", pcmk_rc_str(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_info("Wait for child process completion failed: %s "
+ CRM_XS " source=signalfd", pcmk_rc_str(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_info("Wait for child process completion failed: %s "
+ CRM_XS " source=read", pcmk_rc_str(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_rc_str(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(void)
+{
+ // 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_info("Wait for child process completion failed: %s "
+ CRM_XS " source=write", pcmk_rc_str(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_info("Wait for child process completion failed: %s "
+ CRM_XS " source=pipe", pcmk_rc_str(errno));
+ return false;
+ }
+
+ rc = pcmk__set_nonblocking(data->pipe_fd[0]);
+ if (rc != pcmk_rc_ok) {
+ crm_info("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_info("Could not set pipe output non-blocking: %s " CRM_XS " rc=%d",
+ pcmk_rc_str(rc), rc);
+ }
+
+ // Set SIGCHLD handler
+ data->sa.sa_handler = (sighandler_t) sigchld_handler;
+ data->sa.sa_flags = 0;
+ sigemptyset(&(data->sa.sa_mask));
+ if (sigaction(SIGCHLD, &(data->sa), &(data->old_sa)) < 0) {
+ crm_info("Wait for child process completion failed: %s "
+ CRM_XS " source=sigaction", pcmk_rc_str(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_rc_str(errno));
+ }
+
+ close_pipe(data->pipe_fd);
+}
+
+#endif
+
+/*!
+ * \internal
+ * \brief Close the two file descriptors of a pipe
+ *
+ * \param[in,out] 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)
+{
+ 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_cancelled(op);
+ 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? "stderr" : "stdout"));
+ 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);
+
+ /* The library caller has better context to know how important the output
+ * is, so log it at info and debug severity here. They can log it again at
+ * higher severity if appropriate.
+ */
+ crm_log_output(LOG_INFO, 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,out] 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)) {
+ const char *kind = services__action_kind(op);
+
+ crm_info("%s %s[%d] timed out after %s",
+ kind, op->id, op->pid, pcmk__readable_interval(op->timeout));
+ services__format_result(op, services__generic_error(op),
+ PCMK_EXEC_TIMEOUT,
+ "%s did not complete within %s",
+ kind, pcmk__readable_interval(op->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 %d (%s)",
+ op->id, op->pid, signo, strsignal(signo));
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_CANCELLED, NULL);
+
+ } else {
+ crm_info("%s[%d] terminated with signal %d (%s)",
+ op->id, op->pid, signo, strsignal(signo));
+ services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "%s interrupted by %s signal",
+ services__action_kind(op), strsignal(signo));
+ }
+
+ 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(const 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(const 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_STATE_UNKNOWN;
+ }
+#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(const 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 Return agent standard's exit status for "not configured"
+ *
+ * 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 configured" errors.
+ *
+ * \param[in] op Action that error is for
+ * \param[in] is_fatal Whether problem is cluster-wide instead of only local
+ *
+ * \return Exit status appropriate to agent standard
+ * \note Actions without a standard will get PCMK_OCF_UNKNOWN_ERROR.
+ */
+int
+services__configuration_error(const svc_action_t *op, bool is_fatal)
+{
+ 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_NOT_CONFIGURED;
+ }
+
+#if SUPPORT_NAGIOS
+ if (pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_NAGIOS, pcmk__str_casei)) {
+ return NAGIOS_STATE_UNKNOWN;
+ }
+#endif
+
+ return is_fatal? PCMK_OCF_NOT_CONFIGURED : PCMK_OCF_INVALID_PARAM;
+}
+
+
+/*!
+ * \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)
+{
+ const char *name = op->opaque->exec;
+
+ if (name == NULL) {
+ name = op->agent;
+ if (name == NULL) {
+ name = op->id;
+ }
+ }
+
+ 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__format_result(op, services__not_installed_error(op),
+ PCMK_EXEC_NOT_INSTALLED, "%s: %s",
+ name, pcmk_rc_str(error));
+ break;
+ case EACCES: /* permission denied (various errors) */
+ case EPERM: /* permission denied (various errors) */
+ services__format_result(op, services__authorization_error(op),
+ PCMK_EXEC_ERROR, "%s: %s",
+ name, pcmk_rc_str(error));
+ break;
+ default:
+ services__set_result(op, services__generic_error(op),
+ PCMK_EXEC_ERROR, pcmk_rc_str(error));
+ }
+}
+
+/*!
+ * \internal
+ * \brief Exit a child process that failed before executing agent
+ *
+ * \param[in] op Action that failed
+ * \param[in] exit_status Exit status code to use
+ * \param[in] exit_reason Exit reason to output if for OCF agent
+ */
+static void
+exit_child(const svc_action_t *op, int exit_status, const char *exit_reason)
+{
+ if ((op != NULL) && (exit_reason != NULL)
+ && pcmk__str_eq(op->standard, PCMK_RESOURCE_CLASS_OCF,
+ pcmk__str_none)) {
+ fprintf(stderr, PCMK_OCF_REASON_PREFIX "%s\n", exit_reason);
+ }
+ _exit(exit_status);
+}
+
+static void
+action_launch_child(svc_action_t *op)
+{
+ int rc;
+
+ /* 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_info("Could not reset scheduling policy for %s", op->id);
+ }
+ }
+#endif
+ if (setpriority(PRIO_PROCESS, 0, 0) == -1) {
+ crm_info("Could not reset process priority 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);
+
+ /* It would be nice if errors in this function could be reported as
+ * execution status (for example, PCMK_EXEC_NO_SECRETS for the secrets error
+ * below) instead of exit status. However, we've already forked, so
+ * exit status is all we have. At least for OCF actions, we can output an
+ * exit reason for the parent to parse.
+ */
+
+#if SUPPORT_CIBSECRETS
+ rc = pcmk__substitute_secrets(op->rsc, op->params);
+ if (rc != pcmk_rc_ok) {
+ if (pcmk__str_eq(op->action, "stop", pcmk__str_casei)) {
+ crm_info("Proceeding with stop operation for %s "
+ "despite being unable to load CIB secrets (%s)",
+ op->rsc, pcmk_rc_str(rc));
+ } else {
+ crm_err("Considering %s unconfigured "
+ "because unable to load CIB secrets: %s",
+ op->rsc, pcmk_rc_str(rc));
+ exit_child(op, services__configuration_error(op, false),
+ "Unable to load CIB secrets");
+ }
+ }
+#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_err("Considering %s unauthorized because could not set "
+ "child group to %d: %s",
+ op->id, op->opaque->gid, strerror(errno));
+ exit_child(op, services__authorization_error(op),
+ "Could not set group for child process");
+ }
+
+ // Erase supplementary group list
+ // (We could do initgroups() if we kept a copy of the username)
+ if (setgroups(0, NULL) < 0) {
+ crm_err("Considering %s unauthorized because could not "
+ "clear supplementary groups: %s", op->id, strerror(errno));
+ exit_child(op, services__authorization_error(op),
+ "Could not clear supplementary groups for child process");
+ }
+
+ // Set effective user
+ if (setuid(op->opaque->uid) < 0) {
+ crm_err("Considering %s unauthorized because could not set user "
+ "to %d: %s", op->id, op->opaque->uid, strerror(errno));
+ exit_child(op, services__authorization_error(op),
+ "Could not set user for child process");
+ }
+ }
+
+ // Execute the agent (doesn't return if successful)
+ execvp(op->opaque->exec, op->opaque->args);
+
+ // An earlier stat() should have avoided most possible errors
+ rc = errno;
+ services__handle_exec_error(op, rc);
+ crm_err("Unable to execute %s: %s", op->id, strerror(rc));
+ exit_child(op, op->rc, "Child process was unable to execute file");
+}
+
+/*!
+ * \internal
+ * \brief Wait for synchronous action to complete, and set its result
+ *
+ * \param[in,out] op Action to wait for
+ * \param[in,out] 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_info("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_info("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__format_result(op, services__generic_error(op),
+ PCMK_EXEC_TIMEOUT,
+ "%s did not exit within specified timeout",
+ services__action_kind(op));
+ crm_info("%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_rc_str(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__format_result(op, services__generic_error(op),
+ PCMK_EXEC_ERROR, "%s interrupted by %s signal",
+ services__action_kind(op), strsignal(signo));
+ crm_info("%s[%d] terminated with signal %d (%s)",
+ op->id, op->pid, signo, strsignal(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,out] 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's pending or because
+ * it failed to execute and was already freed)
+ *
+ * \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_info("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_info("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_info("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_info("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_info("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_rc_str(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_rc_str(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_rc_str(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_info("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_info("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_info("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);
+ }
+}
+
+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;
+}
diff --git a/lib/services/services_lsb.c b/lib/services/services_lsb.c
new file mode 100644
index 0000000..134cc70
--- /dev/null
+++ b/lib/services/services_lsb.c
@@ -0,0 +1,341 @@
+/*
+ * Copyright 2010-2022 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 <stdio.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+#include <crm/crm.h>
+#include <crm/services.h>
+#include "services_private.h"
+#include "services_lsb.h"
+
+#define lsb_metadata_template \
+ "<?xml version='1.0'?>\n" \
+ "<!DOCTYPE resource-agent SYSTEM 'ra-api-1.dtd'>\n" \
+ "<resource-agent name='%s' version='" PCMK_DEFAULT_AGENT_VERSION "'>\n" \
+ " <version>1.0</version>\n" \
+ " <longdesc lang='en'>\n" \
+ "%s" \
+ " </longdesc>\n" \
+ " <shortdesc lang='en'>%s</shortdesc>\n" \
+ " <parameters>\n" \
+ " </parameters>\n" \
+ " <actions>\n" \
+ " <action name='meta-data' timeout='5' />\n" \
+ " <action name='start' timeout='15' />\n" \
+ " <action name='stop' timeout='15' />\n" \
+ " <action name='status' timeout='15' />\n" \
+ " <action name='restart' timeout='15' />\n" \
+ " <action name='force-reload' timeout='15' />\n" \
+ " <action name='monitor' timeout='15' interval='15' />\n" \
+ " </actions>\n" \
+ " <special tag='LSB'>\n" \
+ " <Provides>%s</Provides>\n" \
+ " <Required-Start>%s</Required-Start>\n" \
+ " <Required-Stop>%s</Required-Stop>\n" \
+ " <Should-Start>%s</Should-Start>\n" \
+ " <Should-Stop>%s</Should-Stop>\n" \
+ " <Default-Start>%s</Default-Start>\n" \
+ " <Default-Stop>%s</Default-Stop>\n" \
+ " </special>\n" \
+ "</resource-agent>\n"
+
+/* See "Comment Conventions for Init Scripts" in the LSB core specification at:
+ * http://refspecs.linuxfoundation.org/lsb.shtml
+ */
+#define LSB_INITSCRIPT_INFOBEGIN_TAG "### BEGIN INIT INFO"
+#define LSB_INITSCRIPT_INFOEND_TAG "### END INIT INFO"
+#define PROVIDES "# Provides:"
+#define REQ_START "# Required-Start:"
+#define REQ_STOP "# Required-Stop:"
+#define SHLD_START "# Should-Start:"
+#define SHLD_STOP "# Should-Stop:"
+#define DFLT_START "# Default-Start:"
+#define DFLT_STOP "# Default-Stop:"
+#define SHORT_DSCR "# Short-Description:"
+#define DESCRIPTION "# Description:"
+
+#define lsb_meta_helper_free_value(m) \
+ do { \
+ if ((m) != NULL) { \
+ xmlFree(m); \
+ (m) = NULL; \
+ } \
+ } while(0)
+
+/*!
+ * \internal
+ * \brief Grab an LSB header value
+ *
+ * \param[in] line Line read from LSB init script
+ * \param[in,out] value If not set, will be set to XML-safe copy of value
+ * \param[in] prefix Set value if line starts with this pattern
+ *
+ * \return TRUE if value was set, FALSE otherwise
+ */
+static inline gboolean
+lsb_meta_helper_get_value(const char *line, char **value, const char *prefix)
+{
+ if (!*value && pcmk__starts_with(line, prefix)) {
+ *value = (char *)xmlEncodeEntitiesReentrant(NULL, BAD_CAST line+strlen(prefix));
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int
+services__get_lsb_metadata(const char *type, char **output)
+{
+ char ra_pathname[PATH_MAX] = { 0, };
+ FILE *fp = NULL;
+ char buffer[1024] = { 0, };
+ char *provides = NULL;
+ char *req_start = NULL;
+ char *req_stop = NULL;
+ char *shld_start = NULL;
+ char *shld_stop = NULL;
+ char *dflt_start = NULL;
+ char *dflt_stop = NULL;
+ char *s_dscrpt = NULL;
+ char *xml_l_dscrpt = NULL;
+ bool in_header = FALSE;
+
+ if (type[0] == '/') {
+ snprintf(ra_pathname, sizeof(ra_pathname), "%s", type);
+ } else {
+ snprintf(ra_pathname, sizeof(ra_pathname), "%s/%s",
+ PCMK__LSB_INIT_DIR, type);
+ }
+
+ crm_trace("Looking into %s", ra_pathname);
+ fp = fopen(ra_pathname, "r");
+ if (fp == NULL) {
+ return -errno;
+ }
+
+ /* Enter into the LSB-compliant comment block */
+ while (fgets(buffer, sizeof(buffer), fp)) {
+
+ // Ignore lines up to and including the block delimiter
+ if (pcmk__starts_with(buffer, LSB_INITSCRIPT_INFOBEGIN_TAG)) {
+ in_header = TRUE;
+ continue;
+ }
+ if (!in_header) {
+ continue;
+ }
+
+ /* Assume each of the following eight arguments contain one line */
+ if (lsb_meta_helper_get_value(buffer, &provides, PROVIDES)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &req_start, REQ_START)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &req_stop, REQ_STOP)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &shld_start, SHLD_START)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &shld_stop, SHLD_STOP)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &dflt_start, DFLT_START)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &dflt_stop, DFLT_STOP)) {
+ continue;
+ }
+ if (lsb_meta_helper_get_value(buffer, &s_dscrpt, SHORT_DSCR)) {
+ continue;
+ }
+
+ /* Long description may cross multiple lines */
+ if ((xml_l_dscrpt == NULL) // haven't already found long description
+ && pcmk__starts_with(buffer, DESCRIPTION)) {
+ bool processed_line = TRUE;
+ GString *desc = g_string_sized_new(2048);
+
+ // Get remainder of description line itself
+ g_string_append(desc, buffer + sizeof(DESCRIPTION) - 1);
+
+ // Read any continuation lines of the description
+ buffer[0] = '\0';
+ while (fgets(buffer, sizeof(buffer), fp)) {
+ if (pcmk__starts_with(buffer, "# ")
+ || pcmk__starts_with(buffer, "#\t")) {
+ /* '#' followed by a tab or more than one space indicates a
+ * continuation of the long description.
+ */
+ g_string_append(desc, buffer + 1);
+ } else {
+ /* This line is not part of the long description,
+ * so continue with normal processing.
+ */
+ processed_line = FALSE;
+ break;
+ }
+ }
+
+ // Make long description safe to use in XML
+ xml_l_dscrpt =
+ (char *) xmlEncodeEntitiesReentrant(NULL,
+ (pcmkXmlStr) desc->str);
+ g_string_free(desc, TRUE);
+
+ if (processed_line) {
+ // We grabbed the line into the long description
+ continue;
+ }
+ }
+
+ // Stop if we leave the header block
+ if (pcmk__starts_with(buffer, LSB_INITSCRIPT_INFOEND_TAG)) {
+ break;
+ }
+ if (buffer[0] != '#') {
+ break;
+ }
+ }
+ fclose(fp);
+
+ *output = crm_strdup_printf(lsb_metadata_template, type,
+ (xml_l_dscrpt? xml_l_dscrpt : type),
+ (s_dscrpt? s_dscrpt : type),
+ (provides? provides : ""),
+ (req_start? req_start : ""),
+ (req_stop? req_stop : ""),
+ (shld_start? shld_start : ""),
+ (shld_stop? shld_stop : ""),
+ (dflt_start? dflt_start : ""),
+ (dflt_stop? dflt_stop : ""));
+
+ lsb_meta_helper_free_value(xml_l_dscrpt);
+ lsb_meta_helper_free_value(s_dscrpt);
+ lsb_meta_helper_free_value(provides);
+ lsb_meta_helper_free_value(req_start);
+ lsb_meta_helper_free_value(req_stop);
+ lsb_meta_helper_free_value(shld_start);
+ lsb_meta_helper_free_value(shld_stop);
+ lsb_meta_helper_free_value(dflt_start);
+ lsb_meta_helper_free_value(dflt_stop);
+
+ crm_trace("Created fake metadata: %llu",
+ (unsigned long long) strlen(*output));
+ return pcmk_ok;
+}
+
+GList *
+services__list_lsb_agents(void)
+{
+ return services_os_get_directory_list(PCMK__LSB_INIT_DIR, TRUE, TRUE);
+}
+
+bool
+services__lsb_agent_exists(const char *agent)
+{
+ bool rc = FALSE;
+ struct stat st;
+ char *path = pcmk__full_path(agent, PCMK__LSB_INIT_DIR);
+
+ rc = (stat(path, &st) == 0);
+ free(path);
+ return rc;
+}
+
+/*!
+ * \internal
+ * \brief Prepare an LSB action
+ *
+ * \param[in,out] op Action to prepare
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+services__lsb_prepare(svc_action_t *op)
+{
+ op->opaque->exec = pcmk__full_path(op->agent, PCMK__LSB_INIT_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)) {
+ return ENOMEM;
+ }
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \internal
+ * \brief Map an LSB result to a standard OCF result
+ *
+ * \param[in] action Action that result is for
+ * \param[in] exit_status LSB agent exit status
+ *
+ * \return Standard OCF result
+ */
+enum ocf_exitcode
+services__lsb2ocf(const char *action, int exit_status)
+{
+ // For non-status actions, LSB and OCF share error codes <= 7
+ if (!pcmk__str_any_of(action, "status", "monitor", NULL)) {
+ if ((exit_status < 0) || (exit_status > PCMK_LSB_NOT_RUNNING)) {
+ return PCMK_OCF_UNKNOWN_ERROR;
+ }
+ return (enum ocf_exitcode) exit_status;
+ }
+
+ // LSB status actions have their own codes
+ switch (exit_status) {
+ case PCMK_LSB_STATUS_OK:
+ return PCMK_OCF_OK;
+
+ case PCMK_LSB_STATUS_NOT_INSTALLED:
+ return PCMK_OCF_NOT_INSTALLED;
+
+ case PCMK_LSB_STATUS_INSUFFICIENT_PRIV:
+ return PCMK_OCF_INSUFFICIENT_PRIV;
+
+ case PCMK_LSB_STATUS_VAR_PID:
+ case PCMK_LSB_STATUS_VAR_LOCK:
+ case PCMK_LSB_STATUS_NOT_RUNNING:
+ return PCMK_OCF_NOT_RUNNING;
+
+ default:
+ return PCMK_OCF_UNKNOWN_ERROR;
+ }
+}
+
+// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
+
+#include <crm/services_compat.h>
+
+svc_action_t *
+services_action_create(const char *name, const char *action,
+ guint interval_ms, int timeout)
+{
+ return resources_action_create(name, PCMK_RESOURCE_CLASS_LSB, NULL, name,
+ action, interval_ms, timeout, NULL, 0);
+}
+
+GList *
+services_list(void)
+{
+ return resources_list_agents(PCMK_RESOURCE_CLASS_LSB, NULL);
+}
+
+// LCOV_EXCL_STOP
+// End deprecated API
diff --git a/lib/services/services_lsb.h b/lib/services/services_lsb.h
new file mode 100644
index 0000000..8174833
--- /dev/null
+++ b/lib/services/services_lsb.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2010-2022 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_LSB__H
+# define SERVICES_LSB__H
+
+G_GNUC_INTERNAL int services__get_lsb_metadata(const char *type, char **output);
+G_GNUC_INTERNAL GList *services__list_lsb_agents(void);
+G_GNUC_INTERNAL bool services__lsb_agent_exists(const char *agent);
+G_GNUC_INTERNAL int services__lsb_prepare(svc_action_t *op);
+
+G_GNUC_INTERNAL
+enum ocf_exitcode services__lsb2ocf(const char *action, int exit_status);
+
+#endif
diff --git a/lib/services/services_nagios.c b/lib/services/services_nagios.c
new file mode 100644
index 0000000..abddca8
--- /dev/null
+++ b/lib/services/services_nagios.c
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2010-2022 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/msg_xml.h>
+#include "crm/common/mainloop.h"
+#include "crm/services.h"
+
+#include "services_private.h"
+#include "services_nagios.h"
+
+/*!
+ * \internal
+ * \brief Prepare a Nagios action
+ *
+ * \param[in,out] op Action to prepare
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+services__nagios_prepare(svc_action_t *op)
+{
+ 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) {
+ return ENOMEM;
+ }
+
+ 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) {
+ return ENOMEM;
+ }
+
+ } else if (op->params != NULL) {
+ 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)) {
+ return E2BIG;
+ }
+
+ 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) {
+ return ENOMEM;
+ }
+ }
+ }
+
+ // Nagios actions don't need to keep the parameters
+ if (op->params != NULL) {
+ g_hash_table_destroy(op->params);
+ op->params = NULL;
+ }
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \internal
+ * \brief Map a Nagios result to a standard OCF result
+ *
+ * \param[in] exit_status Nagios exit status
+ *
+ * \return Standard OCF result
+ */
+enum ocf_exitcode
+services__nagios2ocf(int exit_status)
+{
+ switch (exit_status) {
+ case NAGIOS_STATE_OK:
+ return PCMK_OCF_OK;
+
+ case NAGIOS_INSUFFICIENT_PRIV:
+ return PCMK_OCF_INSUFFICIENT_PRIV;
+
+ case NAGIOS_STATE_WARNING:
+ return PCMK_OCF_DEGRADED;
+
+ case NAGIOS_STATE_CRITICAL:
+ case NAGIOS_STATE_UNKNOWN:
+ default:
+ return PCMK_OCF_UNKNOWN_ERROR;
+ }
+}
+
+static inline char *
+nagios_metadata_name(const char *plugin)
+{
+ return crm_strdup_printf(NAGIOS_METADATA_DIR "/%s.xml", plugin);
+}
+
+GList *
+services__list_nagios_agents(void)
+{
+ GList *plugin_list = NULL;
+ GList *result = NULL;
+
+ plugin_list = services_os_get_directory_list(NAGIOS_PLUGIN_DIR, TRUE, TRUE);
+
+ // Return only the plugins that have metadata
+ for (GList *gIter = plugin_list; gIter != NULL; gIter = gIter->next) {
+ struct stat st;
+ const char *plugin = gIter->data;
+ char *metadata = nagios_metadata_name(plugin);
+
+ if (stat(metadata, &st) == 0) {
+ result = g_list_append(result, strdup(plugin));
+ }
+ free(metadata);
+ }
+ g_list_free_full(plugin_list, free);
+ return result;
+}
+
+gboolean
+services__nagios_agent_exists(const char *name)
+{
+ char *buf = NULL;
+ gboolean rc = FALSE;
+ struct stat st;
+
+ if (name == NULL) {
+ return rc;
+ }
+
+ buf = crm_strdup_printf(NAGIOS_PLUGIN_DIR "/%s", name);
+ if (stat(buf, &st) == 0) {
+ rc = TRUE;
+ }
+
+ free(buf);
+ return rc;
+}
+
+int
+services__get_nagios_metadata(const char *type, char **output)
+{
+ int rc = pcmk_ok;
+ FILE *file_strm = NULL;
+ int start = 0, length = 0, read_len = 0;
+ char *metadata_file = nagios_metadata_name(type);
+
+ file_strm = fopen(metadata_file, "r");
+ if (file_strm == NULL) {
+ crm_err("Metadata file %s does not exist", metadata_file);
+ free(metadata_file);
+ return -EIO;
+ }
+
+ /* see how big the file is */
+ start = ftell(file_strm);
+ fseek(file_strm, 0L, SEEK_END);
+ length = ftell(file_strm);
+ fseek(file_strm, 0L, start);
+
+ CRM_ASSERT(length >= 0);
+ CRM_ASSERT(start == ftell(file_strm));
+
+ if (length <= 0) {
+ crm_info("%s was not valid", metadata_file);
+ free(*output);
+ *output = NULL;
+ rc = -EIO;
+
+ } else {
+ crm_trace("Reading %d bytes from file", length);
+ *output = calloc(1, (length + 1));
+ read_len = fread(*output, 1, length, file_strm);
+ if (read_len != length) {
+ crm_err("Calculated and read bytes differ: %d vs. %d",
+ length, read_len);
+ free(*output);
+ *output = NULL;
+ rc = -EIO;
+ }
+ }
+
+ fclose(file_strm);
+ free(metadata_file);
+ return rc;
+}
diff --git a/lib/services/services_nagios.h b/lib/services/services_nagios.h
new file mode 100644
index 0000000..2e447e0
--- /dev/null
+++ b/lib/services/services_nagios.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+#ifndef SERVICES_NAGIOS__H
+# define SERVICES_NAGIOS__H
+
+G_GNUC_INTERNAL
+int services__nagios_prepare(svc_action_t *op);
+
+G_GNUC_INTERNAL
+enum ocf_exitcode services__nagios2ocf(int exit_status);
+
+G_GNUC_INTERNAL
+GList *services__list_nagios_agents(void);
+
+G_GNUC_INTERNAL
+gboolean services__nagios_agent_exists(const char *agent);
+
+G_GNUC_INTERNAL
+int services__get_nagios_metadata(const char *type, char **output);
+
+#endif
diff --git a/lib/services/services_ocf.c b/lib/services/services_ocf.c
new file mode 100644
index 0000000..d7fb9bd
--- /dev/null
+++ b/lib/services/services_ocf.c
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2012-2022 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;
+}
+
+/*!
+ * \internal
+ * \brief Prepare an OCF action
+ *
+ * \param[in,out] op Action to prepare
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+services__ocf_prepare(svc_action_t *op)
+{
+ char *dirs = strdup(OCF_RA_PATH);
+ struct stat st;
+
+ if (dirs == NULL) {
+ return ENOMEM;
+ }
+
+ // Look for agent on path
+ for (char *dir = strtok(dirs, ":"); dir != NULL; dir = strtok(NULL, ":")) {
+ char *buf = crm_strdup_printf("%s/%s/%s", dir, op->provider, op->agent);
+
+ if (stat(buf, &st) == 0) {
+ op->opaque->exec = buf;
+ break;
+ }
+ free(buf);
+ }
+ free(dirs);
+
+ if (op->opaque->exec == NULL) {
+ return ENOENT;
+ }
+
+ 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)) {
+ return ENOMEM;
+ }
+
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \internal
+ * \brief Map an actual OCF result to a standard OCF result
+ *
+ * \param[in] exit_status Actual OCF agent exit status
+ *
+ * \return Standard OCF result
+ */
+enum ocf_exitcode
+services__ocf2ocf(int exit_status)
+{
+ switch (exit_status) {
+ case PCMK_OCF_DEGRADED:
+ case PCMK_OCF_DEGRADED_PROMOTED:
+ break;
+ default:
+ if ((exit_status < 0) || (exit_status > PCMK_OCF_FAILED_PROMOTED)) {
+ exit_status = PCMK_OCF_UNKNOWN_ERROR;
+ }
+ break;
+ }
+ return (enum ocf_exitcode) exit_status;
+}
diff --git a/lib/services/services_ocf.h b/lib/services/services_ocf.h
new file mode 100644
index 0000000..1c40552
--- /dev/null
+++ b/lib/services/services_ocf.h
@@ -0,0 +1,31 @@
+/*
+ * 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);
+
+G_GNUC_INTERNAL
+int services__ocf_prepare(svc_action_t *op);
+
+G_GNUC_INTERNAL
+enum ocf_exitcode services__ocf2ocf(int exit_status);
+
+#endif // PCMK__SERVICES_OCF__H
diff --git a/lib/services/services_private.h b/lib/services/services_private.h
new file mode 100644
index 0000000..48269b8
--- /dev/null
+++ b/lib/services/services_private.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2010-2011 Red Hat, Inc.
+ * Later changes copyright 2012-2022 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 HAVE_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 HAVE_DBUS
+ DBusPendingCall* pending;
+ unsigned timerid;
+#endif
+};
+
+G_GNUC_INTERNAL
+const char *services__action_kind(const svc_action_t *action);
+
+G_GNUC_INTERNAL
+GList *services_os_get_single_directory_list(const char *root, gboolean files,
+ gboolean executable);
+
+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);
+
+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(const svc_action_t *op);
+
+G_GNUC_INTERNAL
+int services__not_installed_error(const svc_action_t *op);
+
+G_GNUC_INTERNAL
+int services__authorization_error(const svc_action_t *op);
+
+G_GNUC_INTERNAL
+int services__configuration_error(const svc_action_t *op, bool is_fatal);
+
+G_GNUC_INTERNAL
+void services__handle_exec_error(svc_action_t * op, int error);
+
+G_GNUC_INTERNAL
+void services__set_cancelled(svc_action_t *action);
+
+G_GNUC_INTERNAL
+void services_add_inflight_op(svc_action_t *op);
+
+G_GNUC_INTERNAL
+void services_untrack_op(const svc_action_t *op);
+
+G_GNUC_INTERNAL
+gboolean is_op_blocked(const char *rsc);
+
+#if HAVE_DBUS
+G_GNUC_INTERNAL
+void services_set_op_pending(svc_action_t *op, DBusPendingCall *pending);
+#endif
+
+#endif /* SERVICES_PRIVATE__H */
diff --git a/lib/services/systemd.c b/lib/services/systemd.c
new file mode 100644
index 0000000..0c38ae0
--- /dev/null
+++ b/lib/services/systemd.c
@@ -0,0 +1,1100 @@
+/*
+ * Copyright 2012-2022 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 <crm/crm.h>
+#include <crm/services.h>
+#include <crm/services_internal.h>
+#include <crm/common/mainloop.h>
+
+#include <sys/stat.h>
+#include <gio/gio.h>
+#include <services_private.h>
+#include <systemd.h>
+#include <dbus/dbus.h>
+#include <pcmk-dbus.h>
+
+static void invoke_unit_by_path(svc_action_t *op, const char *unit);
+
+#define BUS_NAME "org.freedesktop.systemd1"
+#define BUS_NAME_MANAGER BUS_NAME ".Manager"
+#define BUS_NAME_UNIT BUS_NAME ".Unit"
+#define BUS_PATH "/org/freedesktop/systemd1"
+
+/*!
+ * \internal
+ * \brief Prepare a systemd action
+ *
+ * \param[in,out] op Action to prepare
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+services__systemd_prepare(svc_action_t *op)
+{
+ op->opaque->exec = strdup("systemd-dbus");
+ if (op->opaque->exec == NULL) {
+ return ENOMEM;
+ }
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \internal
+ * \brief Map a systemd result to a standard OCF result
+ *
+ * \param[in] exit_status Systemd result
+ *
+ * \return Standard OCF result
+ */
+enum ocf_exitcode
+services__systemd2ocf(int exit_status)
+{
+ // This library uses OCF codes for systemd actions
+ return (enum ocf_exitcode) exit_status;
+}
+
+static inline DBusMessage *
+systemd_new_method(const char *method)
+{
+ crm_trace("Calling: %s on " BUS_NAME_MANAGER, method);
+ return dbus_message_new_method_call(BUS_NAME, BUS_PATH, BUS_NAME_MANAGER,
+ method);
+}
+
+/*
+ * Functions to manage a static DBus connection
+ */
+
+static DBusConnection* systemd_proxy = NULL;
+
+static inline DBusPendingCall *
+systemd_send(DBusMessage *msg,
+ void(*done)(DBusPendingCall *pending, void *user_data),
+ void *user_data, int timeout)
+{
+ return pcmk_dbus_send(msg, systemd_proxy, done, user_data, timeout);
+}
+
+static inline DBusMessage *
+systemd_send_recv(DBusMessage *msg, DBusError *error, int timeout)
+{
+ return pcmk_dbus_send_recv(msg, systemd_proxy, error, timeout);
+}
+
+/*!
+ * \internal
+ * \brief Send a method to systemd without arguments, and wait for reply
+ *
+ * \param[in] method Method to send
+ *
+ * \return Systemd reply on success, NULL (and error will be logged) otherwise
+ *
+ * \note The caller must call dbus_message_unref() on the reply after
+ * handling it.
+ */
+static DBusMessage *
+systemd_call_simple_method(const char *method)
+{
+ DBusMessage *msg = systemd_new_method(method);
+ DBusMessage *reply = NULL;
+ DBusError error;
+
+ /* Don't call systemd_init() here, because that calls this */
+ CRM_CHECK(systemd_proxy, return NULL);
+
+ if (msg == NULL) {
+ crm_err("Could not create message to send %s to systemd", method);
+ return NULL;
+ }
+
+ dbus_error_init(&error);
+ reply = systemd_send_recv(msg, &error, DBUS_TIMEOUT_USE_DEFAULT);
+ dbus_message_unref(msg);
+
+ if (dbus_error_is_set(&error)) {
+ crm_err("Could not send %s to systemd: %s (%s)",
+ method, error.message, error.name);
+ dbus_error_free(&error);
+ return NULL;
+
+ } else if (reply == NULL) {
+ crm_err("Could not send %s to systemd: no reply received", method);
+ return NULL;
+ }
+
+ return reply;
+}
+
+static gboolean
+systemd_init(void)
+{
+ static int need_init = 1;
+ // https://dbus.freedesktop.org/doc/api/html/group__DBusConnection.html
+
+ if (systemd_proxy
+ && dbus_connection_get_is_connected(systemd_proxy) == FALSE) {
+ crm_warn("Connection to System DBus is closed. Reconnecting...");
+ pcmk_dbus_disconnect(systemd_proxy);
+ systemd_proxy = NULL;
+ need_init = 1;
+ }
+
+ if (need_init) {
+ need_init = 0;
+ systemd_proxy = pcmk_dbus_connect();
+ }
+ if (systemd_proxy == NULL) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static inline char *
+systemd_get_property(const char *unit, const char *name,
+ void (*callback)(const char *name, const char *value, void *userdata),
+ void *userdata, DBusPendingCall **pending, int timeout)
+{
+ return systemd_proxy?
+ pcmk_dbus_get_property(systemd_proxy, BUS_NAME, unit, BUS_NAME_UNIT,
+ name, callback, userdata, pending, timeout)
+ : NULL;
+}
+
+void
+systemd_cleanup(void)
+{
+ if (systemd_proxy) {
+ pcmk_dbus_disconnect(systemd_proxy);
+ systemd_proxy = NULL;
+ }
+}
+
+/*
+ * end of systemd_proxy functions
+ */
+
+/*!
+ * \internal
+ * \brief Check whether a file name represents a manageable systemd unit
+ *
+ * \param[in] name File name to check
+ *
+ * \return Pointer to "dot" before filename extension if so, NULL otherwise
+ */
+static const char *
+systemd_unit_extension(const char *name)
+{
+ if (name) {
+ const char *dot = strrchr(name, '.');
+
+ if (dot && (!strcmp(dot, ".service")
+ || !strcmp(dot, ".socket")
+ || !strcmp(dot, ".mount")
+ || !strcmp(dot, ".timer")
+ || !strcmp(dot, ".path"))) {
+ return dot;
+ }
+ }
+ return NULL;
+}
+
+static char *
+systemd_service_name(const char *name, bool add_instance_name)
+{
+ const char *dot = NULL;
+
+ if (pcmk__str_empty(name)) {
+ return NULL;
+ }
+
+ /* Services that end with an @ sign are systemd templates. They expect an
+ * instance name to follow the service name. If no instance name was
+ * provided, just add "pacemaker" to the string as the instance name. It
+ * doesn't seem to matter for purposes of looking up whether a service
+ * exists or not.
+ *
+ * A template can be specified either with or without the unit extension,
+ * so this block handles both cases.
+ */
+ dot = systemd_unit_extension(name);
+
+ if (dot) {
+ if (dot != name && *(dot-1) == '@') {
+ char *s = NULL;
+
+ if (asprintf(&s, "%.*spacemaker%s", (int) (dot-name), name, dot) == -1) {
+ /* If asprintf fails, just return name. */
+ return strdup(name);
+ }
+
+ return s;
+ } else {
+ return strdup(name);
+ }
+
+ } else if (add_instance_name && *(name+strlen(name)-1) == '@') {
+ return crm_strdup_printf("%spacemaker.service", name);
+
+ } else {
+ return crm_strdup_printf("%s.service", name);
+ }
+}
+
+static void
+systemd_daemon_reload_complete(DBusPendingCall *pending, void *user_data)
+{
+ DBusError error;
+ DBusMessage *reply = NULL;
+ unsigned int reload_count = GPOINTER_TO_UINT(user_data);
+
+ dbus_error_init(&error);
+ if(pending) {
+ reply = dbus_pending_call_steal_reply(pending);
+ }
+
+ if (pcmk_dbus_find_error(pending, reply, &error)) {
+ crm_warn("Could not issue systemd reload %d: %s",
+ reload_count, error.message);
+ dbus_error_free(&error);
+
+ } else {
+ crm_trace("Reload %d complete", reload_count);
+ }
+
+ if(pending) {
+ dbus_pending_call_unref(pending);
+ }
+ if(reply) {
+ dbus_message_unref(reply);
+ }
+}
+
+static bool
+systemd_daemon_reload(int timeout)
+{
+ static unsigned int reload_count = 0;
+ DBusMessage *msg = systemd_new_method("Reload");
+
+ reload_count++;
+ CRM_ASSERT(msg != NULL);
+ systemd_send(msg, systemd_daemon_reload_complete,
+ GUINT_TO_POINTER(reload_count), timeout);
+ dbus_message_unref(msg);
+
+ return TRUE;
+}
+
+/*!
+ * \internal
+ * \brief Set an action result based on a method error
+ *
+ * \param[in,out] op Action to set result for
+ * \param[in] error Method error
+ */
+static void
+set_result_from_method_error(svc_action_t *op, const DBusError *error)
+{
+ services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "Unable to invoke systemd DBus method");
+
+ if (strstr(error->name, "org.freedesktop.systemd1.InvalidName")
+ || strstr(error->name, "org.freedesktop.systemd1.LoadFailed")
+ || strstr(error->name, "org.freedesktop.systemd1.NoSuchUnit")) {
+
+ if (pcmk__str_eq(op->action, "stop", pcmk__str_casei)) {
+ crm_trace("Masking systemd stop failure (%s) for %s "
+ "because unknown service can be considered stopped",
+ error->name, pcmk__s(op->rsc, "unknown resource"));
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+ return;
+ }
+
+ services__format_result(op, PCMK_OCF_NOT_INSTALLED,
+ PCMK_EXEC_NOT_INSTALLED,
+ "systemd unit %s not found", op->agent);
+ }
+
+ crm_info("DBus request for %s of systemd unit %s%s%s failed: %s",
+ op->action, op->agent,
+ ((op->rsc == NULL)? "" : " for resource "), pcmk__s(op->rsc, ""),
+ error->message);
+}
+
+/*!
+ * \internal
+ * \brief Extract unit path from LoadUnit reply, and execute action
+ *
+ * \param[in] reply LoadUnit reply
+ * \param[in,out] op Action to execute (or NULL to just return path)
+ *
+ * \return DBus object path for specified unit if successful (only valid for
+ * lifetime of \p reply), otherwise NULL
+ */
+static const char *
+execute_after_loadunit(DBusMessage *reply, svc_action_t *op)
+{
+ const char *path = NULL;
+ DBusError error;
+
+ /* path here is not used other than as a non-NULL flag to indicate that a
+ * request was indeed sent
+ */
+ if (pcmk_dbus_find_error((void *) &path, reply, &error)) {
+ if (op != NULL) {
+ set_result_from_method_error(op, &error);
+ }
+ dbus_error_free(&error);
+
+ } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
+ __func__, __LINE__)) {
+ if (op != NULL) {
+ services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "systemd DBus method had unexpected reply");
+ crm_info("Could not load systemd unit %s for %s: "
+ "DBus reply has unexpected type", op->agent, op->id);
+ } else {
+ crm_info("Could not load systemd unit: "
+ "DBus reply has unexpected type");
+ }
+
+ } else {
+ dbus_message_get_args (reply, NULL,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID);
+ }
+
+ if (op != NULL) {
+ if (path != NULL) {
+ invoke_unit_by_path(op, path);
+
+ } else if (!(op->synchronous)) {
+ services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "No DBus object found for systemd unit %s",
+ op->agent);
+ services__finalize_async_op(op);
+ }
+ }
+
+ return path;
+}
+
+/*!
+ * \internal
+ * \brief Execute a systemd action after its LoadUnit completes
+ *
+ * \param[in,out] pending If not NULL, DBus call associated with LoadUnit
+ * \param[in,out] user_data Action to execute
+ */
+static void
+loadunit_completed(DBusPendingCall *pending, void *user_data)
+{
+ DBusMessage *reply = NULL;
+ svc_action_t *op = user_data;
+
+ crm_trace("LoadUnit result for %s arrived", op->id);
+
+ // Grab the reply
+ if (pending != NULL) {
+ reply = dbus_pending_call_steal_reply(pending);
+ }
+
+ // The call is no longer pending
+ CRM_LOG_ASSERT(pending == op->opaque->pending);
+ services_set_op_pending(op, NULL);
+
+ // Execute the desired action based on the reply
+ execute_after_loadunit(reply, user_data);
+ if (reply != NULL) {
+ dbus_message_unref(reply);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Execute a systemd action, given the unit name
+ *
+ * \param[in] arg_name Unit name (possibly without ".service" extension)
+ * \param[in,out] op Action to execute (if NULL, just get object path)
+ * \param[out] path If non-NULL and \p op is NULL or synchronous, where
+ * to store DBus object path for specified unit
+ *
+ * \return Standard Pacemaker return code (for NULL \p op, pcmk_rc_ok means unit
+ * was found; for synchronous actions, pcmk_rc_ok means unit was
+ * executed, with the actual result stored in \p op; for asynchronous
+ * actions, pcmk_rc_ok means action was initiated)
+ * \note It is the caller's responsibility to free the path.
+ */
+static int
+invoke_unit_by_name(const char *arg_name, svc_action_t *op, char **path)
+{
+ DBusMessage *msg;
+ DBusMessage *reply = NULL;
+ DBusPendingCall *pending = NULL;
+ char *name = NULL;
+
+ if (!systemd_init()) {
+ if (op != NULL) {
+ services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "No DBus connection");
+ }
+ return ENOTCONN;
+ }
+
+ /* Create a LoadUnit DBus method (equivalent to GetUnit if already loaded),
+ * which makes the unit usable via further DBus methods.
+ *
+ * <method name="LoadUnit">
+ * <arg name="name" type="s" direction="in"/>
+ * <arg name="unit" type="o" direction="out"/>
+ * </method>
+ */
+ msg = systemd_new_method("LoadUnit");
+ CRM_ASSERT(msg != NULL);
+
+ // Add the (expanded) unit name as the argument
+ name = systemd_service_name(arg_name, op == NULL || pcmk__str_eq(op->action, "meta-data", pcmk__str_none));
+ CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_INVALID));
+ free(name);
+
+ if ((op == NULL) || op->synchronous) {
+ // For synchronous ops, wait for a reply and extract the result
+ const char *unit = NULL;
+ int rc = pcmk_rc_ok;
+
+ reply = systemd_send_recv(msg, NULL,
+ (op? op->timeout : DBUS_TIMEOUT_USE_DEFAULT));
+ dbus_message_unref(msg);
+
+ unit = execute_after_loadunit(reply, op);
+ if (unit == NULL) {
+ rc = ENOENT;
+ if (path != NULL) {
+ *path = NULL;
+ }
+ } else if (path != NULL) {
+ *path = strdup(unit);
+ if (*path == NULL) {
+ rc = ENOMEM;
+ }
+ }
+
+ if (reply != NULL) {
+ dbus_message_unref(reply);
+ }
+ return rc;
+ }
+
+ // For asynchronous ops, initiate the LoadUnit call and return
+ pending = systemd_send(msg, loadunit_completed, op, op->timeout);
+ if (pending == NULL) {
+ services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "Unable to send DBus message");
+ dbus_message_unref(msg);
+ return ECOMM;
+ }
+
+ // LoadUnit was successfully initiated
+ services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
+ services_set_op_pending(op, pending);
+ dbus_message_unref(msg);
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \internal
+ * \brief Compare two strings alphabetically (case-insensitive)
+ *
+ * \param[in] a First string to compare
+ * \param[in] b Second string to compare
+ *
+ * \return 0 if strings are equal, -1 if a < b, 1 if a > b
+ *
+ * \note Usable as a GCompareFunc with g_list_sort().
+ * NULL is considered less than non-NULL.
+ */
+static gint
+sort_str(gconstpointer a, gconstpointer b)
+{
+ if (!a && !b) {
+ return 0;
+ } else if (!a) {
+ return -1;
+ } else if (!b) {
+ return 1;
+ }
+ return strcasecmp(a, b);
+}
+
+GList *
+systemd_unit_listall(void)
+{
+ int nfiles = 0;
+ GList *units = NULL;
+ DBusMessageIter args;
+ DBusMessageIter unit;
+ DBusMessageIter elem;
+ DBusMessage *reply = NULL;
+
+ if (systemd_init() == FALSE) {
+ return NULL;
+ }
+
+/*
+ " <method name=\"ListUnitFiles\">\n" \
+ " <arg name=\"files\" type=\"a(ss)\" direction=\"out\"/>\n" \
+ " </method>\n" \
+*/
+
+ reply = systemd_call_simple_method("ListUnitFiles");
+ if (reply == NULL) {
+ return NULL;
+ }
+ if (!dbus_message_iter_init(reply, &args)) {
+ crm_err("Could not list systemd unit files: systemd reply has no arguments");
+ dbus_message_unref(reply);
+ return NULL;
+ }
+ if (!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_ARRAY,
+ __func__, __LINE__)) {
+ crm_err("Could not list systemd unit files: systemd reply has invalid arguments");
+ dbus_message_unref(reply);
+ return NULL;
+ }
+
+ dbus_message_iter_recurse(&args, &unit);
+ for (; dbus_message_iter_get_arg_type(&unit) != DBUS_TYPE_INVALID;
+ dbus_message_iter_next(&unit)) {
+
+ DBusBasicValue value;
+ const char *match = NULL;
+ char *unit_name = NULL;
+ char *basename = NULL;
+
+ if(!pcmk_dbus_type_check(reply, &unit, DBUS_TYPE_STRUCT, __func__, __LINE__)) {
+ crm_warn("Skipping systemd reply argument with unexpected type");
+ continue;
+ }
+
+ dbus_message_iter_recurse(&unit, &elem);
+ if(!pcmk_dbus_type_check(reply, &elem, DBUS_TYPE_STRING, __func__, __LINE__)) {
+ crm_warn("Skipping systemd reply argument with no string");
+ continue;
+ }
+
+ dbus_message_iter_get_basic(&elem, &value);
+ if (value.str == NULL) {
+ crm_debug("ListUnitFiles reply did not provide a string");
+ continue;
+ }
+ crm_trace("DBus ListUnitFiles listed: %s", value.str);
+
+ match = systemd_unit_extension(value.str);
+ if (match == NULL) {
+ // This is not a unit file type we know how to manage
+ crm_debug("ListUnitFiles entry '%s' is not supported as resource",
+ value.str);
+ continue;
+ }
+
+ // ListUnitFiles returns full path names, we just want base name
+ basename = strrchr(value.str, '/');
+ if (basename) {
+ basename = basename + 1;
+ } else {
+ basename = value.str;
+ }
+
+ if (!strcmp(match, ".service")) {
+ // Service is the "default" unit type, so strip it
+ unit_name = strndup(basename, match - basename);
+ } else {
+ unit_name = strdup(basename);
+ }
+
+ nfiles++;
+ units = g_list_prepend(units, unit_name);
+ }
+
+ dbus_message_unref(reply);
+
+ crm_trace("Found %d manageable systemd unit files", nfiles);
+ units = g_list_sort(units, sort_str);
+ return units;
+}
+
+gboolean
+systemd_unit_exists(const char *name)
+{
+ char *path = NULL;
+ char *state = NULL;
+
+ /* Note: Makes a blocking dbus calls
+ * Used by resources_find_service_class() when resource class=service
+ */
+ if ((invoke_unit_by_name(name, NULL, &path) != pcmk_rc_ok)
+ || (path == NULL)) {
+ return FALSE;
+ }
+
+ /* A successful LoadUnit is not sufficient to determine the unit's
+ * existence; it merely means the LoadUnit request received a reply.
+ * We must make another blocking call to check the LoadState property.
+ */
+ state = systemd_get_property(path, "LoadState", NULL, NULL, NULL,
+ DBUS_TIMEOUT_USE_DEFAULT);
+ free(path);
+ if (pcmk__str_any_of(state, "loaded", "masked", NULL)) {
+ free(state);
+ return TRUE;
+ }
+ free(state);
+ return FALSE;
+}
+
+#define METADATA_FORMAT \
+ "<?xml version=\"1.0\"?>\n" \
+ "<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n" \
+ "<resource-agent name=\"%s\" version=\"" PCMK_DEFAULT_AGENT_VERSION "\">\n" \
+ " <version>1.1</version>\n" \
+ " <longdesc lang=\"en\">\n" \
+ " %s\n" \
+ " </longdesc>\n" \
+ " <shortdesc lang=\"en\">systemd unit file for %s</shortdesc>\n" \
+ " <parameters/>\n" \
+ " <actions>\n" \
+ " <action name=\"start\" timeout=\"100\" />\n" \
+ " <action name=\"stop\" timeout=\"100\" />\n" \
+ " <action name=\"status\" timeout=\"100\" />\n" \
+ " <action name=\"monitor\" timeout=\"100\" interval=\"60\"/>\n" \
+ " <action name=\"meta-data\" timeout=\"5\" />\n" \
+ " </actions>\n" \
+ " <special tag=\"systemd\"/>\n" \
+ "</resource-agent>\n"
+
+static char *
+systemd_unit_metadata(const char *name, int timeout)
+{
+ char *meta = NULL;
+ char *desc = NULL;
+ char *path = NULL;
+
+ char *escaped = NULL;
+
+ if (invoke_unit_by_name(name, NULL, &path) == pcmk_rc_ok) {
+ /* TODO: Worth a making blocking call for? Probably not. Possibly if cached. */
+ desc = systemd_get_property(path, "Description", NULL, NULL, NULL,
+ timeout);
+ } else {
+ desc = crm_strdup_printf("Systemd unit file for %s", name);
+ }
+
+ escaped = crm_xml_escape(desc);
+
+ meta = crm_strdup_printf(METADATA_FORMAT, name, escaped, name);
+ free(desc);
+ free(path);
+ free(escaped);
+ return meta;
+}
+
+/*!
+ * \internal
+ * \brief Determine result of method from reply
+ *
+ * \param[in] reply Reply to start, stop, or restart request
+ * \param[in,out] op Action that was executed
+ */
+static void
+process_unit_method_reply(DBusMessage *reply, svc_action_t *op)
+{
+ DBusError error;
+
+ /* The first use of error here is not used other than as a non-NULL flag to
+ * indicate that a request was indeed sent
+ */
+ if (pcmk_dbus_find_error((void *) &error, reply, &error)) {
+ set_result_from_method_error(op, &error);
+ dbus_error_free(&error);
+
+ } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
+ __func__, __LINE__)) {
+ crm_info("DBus request for %s of %s succeeded but "
+ "return type was unexpected",
+ op->action, pcmk__s(op->rsc, "unknown resource"));
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE,
+ "systemd DBus method had unexpected reply");
+
+ } else {
+ const char *path = NULL;
+
+ dbus_message_get_args(reply, NULL,
+ DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID);
+ crm_debug("DBus request for %s of %s using %s succeeded",
+ op->action, pcmk__s(op->rsc, "unknown resource"), path);
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Process the completion of an asynchronous unit start, stop, or restart
+ *
+ * \param[in,out] pending If not NULL, DBus call associated with request
+ * \param[in,out] user_data Action that was executed
+ */
+static void
+unit_method_complete(DBusPendingCall *pending, void *user_data)
+{
+ DBusMessage *reply = NULL;
+ svc_action_t *op = user_data;
+
+ crm_trace("Result for %s arrived", op->id);
+
+ // Grab the reply
+ if (pending != NULL) {
+ reply = dbus_pending_call_steal_reply(pending);
+ }
+
+ // The call is no longer pending
+ CRM_LOG_ASSERT(pending == op->opaque->pending);
+ services_set_op_pending(op, NULL);
+
+ // Determine result and finalize action
+ process_unit_method_reply(reply, op);
+ services__finalize_async_op(op);
+ if (reply != NULL) {
+ dbus_message_unref(reply);
+ }
+}
+
+#define SYSTEMD_OVERRIDE_ROOT "/run/systemd/system/"
+
+/* When the cluster manages a systemd resource, we create a unit file override
+ * to order the service "before" pacemaker. The "before" relationship won't
+ * actually be used, since systemd won't ever start the resource -- we're
+ * interested in the reverse shutdown ordering it creates, to ensure that
+ * systemd doesn't stop the resource at shutdown while pacemaker is still
+ * running.
+ *
+ * @TODO Add start timeout
+ */
+#define SYSTEMD_OVERRIDE_TEMPLATE \
+ "[Unit]\n" \
+ "Description=Cluster Controlled %s\n" \
+ "Before=pacemaker.service pacemaker_remote.service\n" \
+ "\n" \
+ "[Service]\n" \
+ "Restart=no\n"
+
+// Temporarily use rwxr-xr-x umask when opening a file for writing
+static FILE *
+create_world_readable(const char *filename)
+{
+ mode_t orig_umask = umask(S_IWGRP | S_IWOTH);
+ FILE *fp = fopen(filename, "w");
+
+ umask(orig_umask);
+ return fp;
+}
+
+static void
+create_override_dir(const char *agent)
+{
+ char *override_dir = crm_strdup_printf(SYSTEMD_OVERRIDE_ROOT
+ "/%s.service.d", agent);
+ int rc = pcmk__build_path(override_dir, 0755);
+
+ if (rc != pcmk_rc_ok) {
+ crm_warn("Could not create systemd override directory %s: %s",
+ override_dir, pcmk_rc_str(rc));
+ }
+ free(override_dir);
+}
+
+static char *
+get_override_filename(const char *agent)
+{
+ return crm_strdup_printf(SYSTEMD_OVERRIDE_ROOT
+ "/%s.service.d/50-pacemaker.conf", agent);
+}
+
+static void
+systemd_create_override(const char *agent, int timeout)
+{
+ FILE *file_strm = NULL;
+ char *override_file = get_override_filename(agent);
+
+ create_override_dir(agent);
+
+ /* Ensure the override file is world-readable. This is not strictly
+ * necessary, but it avoids a systemd warning in the logs.
+ */
+ file_strm = create_world_readable(override_file);
+ if (file_strm == NULL) {
+ crm_err("Cannot open systemd override file %s for writing",
+ override_file);
+ } else {
+ char *override = crm_strdup_printf(SYSTEMD_OVERRIDE_TEMPLATE, agent);
+
+ int rc = fprintf(file_strm, "%s\n", override);
+
+ free(override);
+ if (rc < 0) {
+ crm_perror(LOG_WARNING, "Cannot write to systemd override file %s",
+ override_file);
+ }
+ fflush(file_strm);
+ fclose(file_strm);
+ systemd_daemon_reload(timeout);
+ }
+
+ free(override_file);
+}
+
+static void
+systemd_remove_override(const char *agent, int timeout)
+{
+ char *override_file = get_override_filename(agent);
+ int rc = unlink(override_file);
+
+ if (rc < 0) {
+ // Stop may be called when already stopped, which is fine
+ crm_perror(LOG_DEBUG, "Cannot remove systemd override file %s",
+ override_file);
+ } else {
+ systemd_daemon_reload(timeout);
+ }
+ free(override_file);
+}
+
+/*!
+ * \internal
+ * \brief Parse result of systemd status check
+ *
+ * Set a status action's exit status and execution status based on a DBus
+ * property check result, and finalize the action if asynchronous.
+ *
+ * \param[in] name DBus interface name for property that was checked
+ * \param[in] state Property value
+ * \param[in,out] userdata Status action that check was done for
+ */
+static void
+parse_status_result(const char *name, const char *state, void *userdata)
+{
+ svc_action_t *op = userdata;
+
+ crm_trace("Resource %s has %s='%s'",
+ pcmk__s(op->rsc, "(unspecified)"), name,
+ pcmk__s(state, "<null>"));
+
+ if (pcmk__str_eq(state, "active", pcmk__str_none)) {
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+
+ } else if (pcmk__str_eq(state, "reloading", pcmk__str_none)) {
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+
+ } else if (pcmk__str_eq(state, "activating", pcmk__str_none)) {
+ services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
+
+ } else if (pcmk__str_eq(state, "deactivating", pcmk__str_none)) {
+ services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
+
+ } else {
+ services__set_result(op, PCMK_OCF_NOT_RUNNING, PCMK_EXEC_DONE, state);
+ }
+
+ if (!(op->synchronous)) {
+ services_set_op_pending(op, NULL);
+ services__finalize_async_op(op);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Invoke a systemd unit, given its DBus object path
+ *
+ * \param[in,out] op Action to execute
+ * \param[in] unit DBus object path of systemd unit to invoke
+ */
+static void
+invoke_unit_by_path(svc_action_t *op, const char *unit)
+{
+ const char *method = NULL;
+ DBusMessage *msg = NULL;
+ DBusMessage *reply = NULL;
+
+ if (pcmk__str_any_of(op->action, "monitor", "status", NULL)) {
+ DBusPendingCall *pending = NULL;
+ char *state;
+
+ state = systemd_get_property(unit, "ActiveState",
+ (op->synchronous? NULL : parse_status_result),
+ op, (op->synchronous? NULL : &pending),
+ op->timeout);
+ if (op->synchronous) {
+ parse_status_result("ActiveState", state, op);
+ free(state);
+
+ } else if (pending == NULL) { // Could not get ActiveState property
+ services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "Could not get state for unit %s from DBus",
+ op->agent);
+ services__finalize_async_op(op);
+
+ } else {
+ services_set_op_pending(op, pending);
+ }
+ return;
+
+ } else if (pcmk__str_eq(op->action, "start", pcmk__str_none)) {
+ method = "StartUnit";
+ systemd_create_override(op->agent, op->timeout);
+
+ } else if (pcmk__str_eq(op->action, "stop", pcmk__str_none)) {
+ method = "StopUnit";
+ systemd_remove_override(op->agent, op->timeout);
+
+ } else if (pcmk__str_eq(op->action, "restart", pcmk__str_none)) {
+ method = "RestartUnit";
+
+ } else {
+ services__format_result(op, PCMK_OCF_UNIMPLEMENT_FEATURE,
+ PCMK_EXEC_ERROR,
+ "Action %s not implemented "
+ "for systemd resources",
+ pcmk__s(op->action, "(unspecified)"));
+ if (!(op->synchronous)) {
+ services__finalize_async_op(op);
+ }
+ return;
+ }
+
+ crm_trace("Calling %s for unit path %s%s%s",
+ method, unit,
+ ((op->rsc == NULL)? "" : " for resource "), pcmk__s(op->rsc, ""));
+
+ msg = systemd_new_method(method);
+ CRM_ASSERT(msg != NULL);
+
+ /* (ss) */
+ {
+ const char *replace_s = "replace";
+ char *name = systemd_service_name(op->agent, pcmk__str_eq(op->action, "meta-data", pcmk__str_none));
+
+ CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &name, DBUS_TYPE_INVALID));
+ CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &replace_s, DBUS_TYPE_INVALID));
+
+ free(name);
+ }
+
+ if (op->synchronous) {
+ reply = systemd_send_recv(msg, NULL, op->timeout);
+ dbus_message_unref(msg);
+ process_unit_method_reply(reply, op);
+ if (reply != NULL) {
+ dbus_message_unref(reply);
+ }
+
+ } else {
+ DBusPendingCall *pending = systemd_send(msg, unit_method_complete, op,
+ op->timeout);
+
+ dbus_message_unref(msg);
+ if (pending == NULL) {
+ services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "Unable to send DBus message");
+ services__finalize_async_op(op);
+
+ } else {
+ services_set_op_pending(op, pending);
+ }
+ }
+}
+
+static gboolean
+systemd_timeout_callback(gpointer p)
+{
+ svc_action_t * op = p;
+
+ op->opaque->timerid = 0;
+ crm_info("%s action for systemd unit %s named '%s' timed out",
+ op->action, op->agent, op->rsc);
+ services__format_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_TIMEOUT,
+ "%s action for systemd unit %s "
+ "did not complete in time", op->action, op->agent);
+ services__finalize_async_op(op);
+ return FALSE;
+}
+
+/*!
+ * \internal
+ * \brief Execute a systemd action
+ *
+ * \param[in,out] 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's pending or because
+ * it failed to execute and was already freed)
+ *
+ * \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_systemd(svc_action_t *op)
+{
+ CRM_ASSERT(op != NULL);
+
+ if ((op->action == NULL) || (op->agent == NULL)) {
+ services__set_result(op, PCMK_OCF_NOT_CONFIGURED, PCMK_EXEC_ERROR_FATAL,
+ "Bug in action caller");
+ goto done;
+ }
+
+ if (!systemd_init()) {
+ services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "No DBus connection");
+ goto done;
+ }
+
+ crm_debug("Performing %ssynchronous %s op on systemd unit %s%s%s",
+ (op->synchronous? "" : "a"), op->action, op->agent,
+ ((op->rsc == NULL)? "" : " for resource "), pcmk__s(op->rsc, ""));
+
+ if (pcmk__str_eq(op->action, "meta-data", pcmk__str_casei)) {
+ op->stdout_data = systemd_unit_metadata(op->agent, op->timeout);
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+ goto done;
+ }
+
+ /* invoke_unit_by_name() should always override these values, which are here
+ * just as a fail-safe in case there are any code paths that neglect to
+ */
+ services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "Bug in service library");
+
+ if (invoke_unit_by_name(op->agent, op, NULL) == pcmk_rc_ok) {
+ op->opaque->timerid = g_timeout_add(op->timeout + 5000,
+ systemd_timeout_callback, op);
+ 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);
+ }
+}
diff --git a/lib/services/systemd.h b/lib/services/systemd.h
new file mode 100644
index 0000000..0d3dbe6
--- /dev/null
+++ b/lib/services/systemd.h
@@ -0,0 +1,30 @@
+/*
+ * 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 SYSTEMD__H
+# define SYSTEMD__H
+
+# include <glib.h>
+# include "crm/services.h"
+
+G_GNUC_INTERNAL GList *systemd_unit_listall(void);
+
+G_GNUC_INTERNAL
+int services__systemd_prepare(svc_action_t *op);
+
+G_GNUC_INTERNAL
+enum ocf_exitcode services__systemd2ocf(int exit_status);
+
+G_GNUC_INTERNAL
+int services__execute_systemd(svc_action_t *op);
+
+G_GNUC_INTERNAL gboolean systemd_unit_exists(const gchar * name);
+G_GNUC_INTERNAL void systemd_cleanup(void);
+
+#endif /* SYSTEMD__H */
diff --git a/lib/services/upstart.c b/lib/services/upstart.c
new file mode 100644
index 0000000..459b572
--- /dev/null
+++ b/lib/services/upstart.c
@@ -0,0 +1,701 @@
+/*
+ * Original copyright 2010 Senko Rasic <senko.rasic@dobarkod.hr>
+ * and Ante Karamatic <ivoks@init.hr>
+ * Later changes copyright 2012-2022 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 <crm/crm.h>
+#include <crm/services.h>
+#include <crm/common/mainloop.h>
+
+#include <services_private.h>
+#include <upstart.h>
+#include <dbus/dbus.h>
+#include <pcmk-dbus.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#define BUS_NAME "com.ubuntu.Upstart"
+#define BUS_PATH "/com/ubuntu/Upstart"
+
+#define UPSTART_06_API BUS_NAME"0_6"
+#define UPSTART_JOB_IFACE UPSTART_06_API".Job"
+#define BUS_PROPERTY_IFACE "org.freedesktop.DBus.Properties"
+
+/*
+ http://upstart.ubuntu.com/wiki/DBusInterface
+*/
+static DBusConnection *upstart_proxy = NULL;
+
+/*!
+ * \internal
+ * \brief Prepare an Upstart action
+ *
+ * \param[in,out] op Action to prepare
+ *
+ * \return Standard Pacemaker return code
+ */
+int
+services__upstart_prepare(svc_action_t *op)
+{
+ op->opaque->exec = strdup("upstart-dbus");
+ if (op->opaque->exec == NULL) {
+ return ENOMEM;
+ }
+ return pcmk_rc_ok;
+}
+
+/*!
+ * \internal
+ * \brief Map a Upstart result to a standard OCF result
+ *
+ * \param[in] exit_status Upstart result
+ *
+ * \return Standard OCF result
+ */
+enum ocf_exitcode
+services__upstart2ocf(int exit_status)
+{
+ // This library uses OCF codes for Upstart actions
+ return (enum ocf_exitcode) exit_status;
+}
+
+static gboolean
+upstart_init(void)
+{
+ static int need_init = 1;
+
+ if (need_init) {
+ need_init = 0;
+ upstart_proxy = pcmk_dbus_connect();
+ }
+ if (upstart_proxy == NULL) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void
+upstart_cleanup(void)
+{
+ if (upstart_proxy) {
+ pcmk_dbus_disconnect(upstart_proxy);
+ upstart_proxy = NULL;
+ }
+}
+
+/*!
+ * \internal
+ * \brief Get the DBus object path corresponding to a job name
+ *
+ * \param[in] arg_name Name of job to get path for
+ * \param[out] path If not NULL, where to store DBus object path
+ * \param[in] timeout Give up after this many seconds
+ *
+ * \return true if object path was found, false otherwise
+ * \note The caller is responsible for freeing *path if it is non-NULL.
+ */
+static bool
+object_path_for_job(const gchar *arg_name, char **path, int timeout)
+{
+ /*
+ com.ubuntu.Upstart0_6.GetJobByName (in String name, out ObjectPath job)
+ */
+ DBusError error;
+ DBusMessage *msg;
+ DBusMessage *reply = NULL;
+ bool rc = false;
+
+ if (path != NULL) {
+ *path = NULL;
+ }
+
+ if (!upstart_init()) {
+ return false;
+ }
+ msg = dbus_message_new_method_call(BUS_NAME, // target for the method call
+ BUS_PATH, // object to call on
+ UPSTART_06_API, // interface to call on
+ "GetJobByName"); // method name
+
+ dbus_error_init(&error);
+ CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_STRING, &arg_name,
+ DBUS_TYPE_INVALID));
+ reply = pcmk_dbus_send_recv(msg, upstart_proxy, &error, timeout);
+ dbus_message_unref(msg);
+
+ if (dbus_error_is_set(&error)) {
+ crm_err("Could not get DBus object path for %s: %s",
+ arg_name, error.message);
+ dbus_error_free(&error);
+
+ } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
+ __func__, __LINE__)) {
+ crm_err("Could not get DBus object path for %s: Invalid return type",
+ arg_name);
+
+ } else {
+ if (path != NULL) {
+ dbus_message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, path,
+ DBUS_TYPE_INVALID);
+ if (*path != NULL) {
+ *path = strdup(*path);
+ }
+ }
+ rc = true;
+ }
+
+ if (reply != NULL) {
+ dbus_message_unref(reply);
+ }
+ return rc;
+}
+
+static void
+fix(char *input, const char *search, char replace)
+{
+ char *match = NULL;
+ int shuffle = strlen(search) - 1;
+
+ while (TRUE) {
+ int len, lpc;
+
+ match = strstr(input, search);
+ if (match == NULL) {
+ break;
+ }
+ crm_trace("Found: %s", match);
+ match[0] = replace;
+ len = strlen(match) - shuffle;
+ for (lpc = 1; lpc <= len; lpc++) {
+ match[lpc] = match[lpc + shuffle];
+ }
+ }
+}
+
+static char *
+fix_upstart_name(const char *input)
+{
+ char *output = strdup(input);
+
+ fix(output, "_2b", '+');
+ fix(output, "_2c", ',');
+ fix(output, "_2d", '-');
+ fix(output, "_2e", '.');
+ fix(output, "_40", '@');
+ fix(output, "_5f", '_');
+ return output;
+}
+
+GList *
+upstart_job_listall(void)
+{
+ GList *units = NULL;
+ DBusMessageIter args;
+ DBusMessageIter unit;
+ DBusMessage *msg = NULL;
+ DBusMessage *reply = NULL;
+ const char *method = "GetAllJobs";
+ DBusError error;
+ int lpc = 0;
+
+ if (upstart_init() == FALSE) {
+ return NULL;
+ }
+
+/*
+ com.ubuntu.Upstart0_6.GetAllJobs (out <Array of ObjectPath> jobs)
+*/
+
+ dbus_error_init(&error);
+ msg = dbus_message_new_method_call(BUS_NAME, // target for the method call
+ BUS_PATH, // object to call on
+ UPSTART_06_API, // interface to call on
+ method); // method name
+ CRM_ASSERT(msg != NULL);
+
+ reply = pcmk_dbus_send_recv(msg, upstart_proxy, &error, DBUS_TIMEOUT_USE_DEFAULT);
+ dbus_message_unref(msg);
+
+ if (dbus_error_is_set(&error)) {
+ crm_err("Call to %s failed: %s", method, error.message);
+ dbus_error_free(&error);
+ return NULL;
+
+ } else if (!dbus_message_iter_init(reply, &args)) {
+ crm_err("Call to %s failed: Message has no arguments", method);
+ dbus_message_unref(reply);
+ return NULL;
+ }
+
+ if(!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_ARRAY, __func__, __LINE__)) {
+ crm_err("Call to %s failed: Message has invalid arguments", method);
+ dbus_message_unref(reply);
+ return NULL;
+ }
+
+ dbus_message_iter_recurse(&args, &unit);
+ while (dbus_message_iter_get_arg_type (&unit) != DBUS_TYPE_INVALID) {
+ DBusBasicValue value;
+ const char *job = NULL;
+ char *path = NULL;
+
+ if(!pcmk_dbus_type_check(reply, &unit, DBUS_TYPE_OBJECT_PATH, __func__, __LINE__)) {
+ crm_warn("Skipping Upstart reply argument with unexpected type");
+ continue;
+ }
+
+ dbus_message_iter_get_basic(&unit, &value);
+
+ if(value.str) {
+ int llpc = 0;
+ path = value.str;
+ job = value.str;
+ while (path[llpc] != 0) {
+ if (path[llpc] == '/') {
+ job = path + llpc + 1;
+ }
+ llpc++;
+ }
+ lpc++;
+ crm_trace("%s -> %s", path, job);
+ units = g_list_append(units, fix_upstart_name(job));
+ }
+ dbus_message_iter_next (&unit);
+ }
+
+ dbus_message_unref(reply);
+ crm_trace("Found %d upstart jobs", lpc);
+ return units;
+}
+
+gboolean
+upstart_job_exists(const char *name)
+{
+ return object_path_for_job(name, NULL, DBUS_TIMEOUT_USE_DEFAULT);
+}
+
+static char *
+get_first_instance(const gchar * job, int timeout)
+{
+ char *instance = NULL;
+ const char *method = "GetAllInstances";
+ DBusError error;
+ DBusMessage *msg;
+ DBusMessage *reply;
+ DBusMessageIter args;
+ DBusMessageIter unit;
+
+ dbus_error_init(&error);
+ msg = dbus_message_new_method_call(BUS_NAME, // target for the method call
+ job, // object to call on
+ UPSTART_JOB_IFACE, // interface to call on
+ method); // method name
+ CRM_ASSERT(msg != NULL);
+
+ dbus_message_append_args(msg, DBUS_TYPE_INVALID);
+ reply = pcmk_dbus_send_recv(msg, upstart_proxy, &error, timeout);
+ dbus_message_unref(msg);
+
+ if (dbus_error_is_set(&error)) {
+ crm_info("Call to %s failed: %s", method, error.message);
+ dbus_error_free(&error);
+ goto done;
+
+ } else if(reply == NULL) {
+ crm_info("Call to %s failed: no reply", method);
+ goto done;
+
+ } else if (!dbus_message_iter_init(reply, &args)) {
+ crm_info("Call to %s failed: Message has no arguments", method);
+ goto done;
+ }
+
+ if(!pcmk_dbus_type_check(reply, &args, DBUS_TYPE_ARRAY, __func__, __LINE__)) {
+ crm_info("Call to %s failed: Message has invalid arguments", method);
+ goto done;
+ }
+
+ dbus_message_iter_recurse(&args, &unit);
+ if(pcmk_dbus_type_check(reply, &unit, DBUS_TYPE_OBJECT_PATH, __func__, __LINE__)) {
+ DBusBasicValue value;
+
+ dbus_message_iter_get_basic(&unit, &value);
+
+ if(value.str) {
+ instance = strdup(value.str);
+ crm_trace("Result: %s", instance);
+ }
+ }
+
+ done:
+ if(reply) {
+ dbus_message_unref(reply);
+ }
+ return instance;
+}
+
+/*!
+ * \internal
+ * \brief Parse result of Upstart status check
+ *
+ * \param[in] name DBus interface name for property that was checked
+ * \param[in] state Property value
+ * \param[in,out] userdata Status action that check was done for
+ */
+static void
+parse_status_result(const char *name, const char *state, void *userdata)
+{
+ svc_action_t *op = userdata;
+
+ if (pcmk__str_eq(state, "running", pcmk__str_none)) {
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+ } else {
+ services__set_result(op, PCMK_OCF_NOT_RUNNING, PCMK_EXEC_DONE, state);
+ }
+
+ if (!(op->synchronous)) {
+ services_set_op_pending(op, NULL);
+ services__finalize_async_op(op);
+ }
+}
+
+#define METADATA_FORMAT \
+ "<?xml version=\"1.0\"?>\n" \
+ "<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n" \
+ "<resource-agent name=\"%s\" version=\"" PCMK_DEFAULT_AGENT_VERSION "\">\n" \
+ " <version>1.1</version>\n" \
+ " <longdesc lang=\"en\">\n" \
+ " Upstart agent for controlling the system %s service\n" \
+ " </longdesc>\n" \
+ " <shortdesc lang=\"en\">Upstart job for %s</shortdesc>\n" \
+ " <parameters/>\n" \
+ " <actions>\n" \
+ " <action name=\"start\" timeout=\"15\" />\n" \
+ " <action name=\"stop\" timeout=\"15\" />\n" \
+ " <action name=\"status\" timeout=\"15\" />\n" \
+ " <action name=\"restart\" timeout=\"15\" />\n" \
+ " <action name=\"monitor\" timeout=\"15\" interval=\"15\" start-delay=\"15\" />\n" \
+ " <action name=\"meta-data\" timeout=\"5\" />\n" \
+ " </actions>\n" \
+ " <special tag=\"upstart\"/>\n" \
+ "</resource-agent>\n"
+
+static char *
+upstart_job_metadata(const char *name)
+{
+ return crm_strdup_printf(METADATA_FORMAT, name, name, name);
+}
+
+/*!
+ * \internal
+ * \brief Set an action result based on a method error
+ *
+ * \param[in,out] op Action to set result for
+ * \param[in] error Method error
+ */
+static void
+set_result_from_method_error(svc_action_t *op, const DBusError *error)
+{
+ services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "Unable to invoke Upstart DBus method");
+
+ if (strstr(error->name, UPSTART_06_API ".Error.UnknownInstance")) {
+
+ if (pcmk__str_eq(op->action, "stop", pcmk__str_casei)) {
+ crm_trace("Masking stop failure (%s) for %s "
+ "because unknown service can be considered stopped",
+ error->name, pcmk__s(op->rsc, "unknown resource"));
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+ return;
+ }
+
+ services__set_result(op, PCMK_OCF_NOT_INSTALLED,
+ PCMK_EXEC_NOT_INSTALLED, "Upstart job not found");
+
+ } else if (pcmk__str_eq(op->action, "start", pcmk__str_casei)
+ && strstr(error->name, UPSTART_06_API ".Error.AlreadyStarted")) {
+ crm_trace("Masking start failure (%s) for %s "
+ "because already started resource is OK",
+ error->name, pcmk__s(op->rsc, "unknown resource"));
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+ return;
+ }
+
+ crm_info("DBus request for %s of Upstart job %s for resource %s failed: %s",
+ op->action, op->agent, pcmk__s(op->rsc, "with unknown name"),
+ error->message);
+}
+
+/*!
+ * \internal
+ * \brief Process the completion of an asynchronous job start, stop, or restart
+ *
+ * \param[in,out] pending If not NULL, DBus call associated with request
+ * \param[in,out] user_data Action that was executed
+ */
+static void
+job_method_complete(DBusPendingCall *pending, void *user_data)
+{
+ DBusError error;
+ DBusMessage *reply = NULL;
+ svc_action_t *op = user_data;
+
+ // Grab the reply
+ if (pending != NULL) {
+ reply = dbus_pending_call_steal_reply(pending);
+ }
+
+ // Determine result
+ dbus_error_init(&error);
+ if (pcmk_dbus_find_error(pending, reply, &error)) {
+ set_result_from_method_error(op, &error);
+ dbus_error_free(&error);
+
+ } else if (pcmk__str_eq(op->action, "stop", pcmk__str_none)) {
+ // Call has no return value
+ crm_debug("DBus request for stop of %s succeeded",
+ pcmk__s(op->rsc, "unknown resource"));
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+
+ } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
+ __func__, __LINE__)) {
+ crm_info("DBus request for %s of %s succeeded but "
+ "return type was unexpected", op->action,
+ pcmk__s(op->rsc, "unknown resource"));
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+
+ } else {
+ const char *path = NULL;
+
+ dbus_message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID);
+ crm_debug("DBus request for %s of %s using %s succeeded",
+ op->action, pcmk__s(op->rsc, "unknown resource"), path);
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+ }
+
+ // The call is no longer pending
+ CRM_LOG_ASSERT(pending == op->opaque->pending);
+ services_set_op_pending(op, NULL);
+
+ // Finalize action
+ services__finalize_async_op(op);
+ if (reply != NULL) {
+ dbus_message_unref(reply);
+ }
+}
+
+/*!
+ * \internal
+ * \brief Execute an Upstart action
+ *
+ * \param[in,out] 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's pending or because
+ * it failed to execute and was already freed)
+ *
+ * \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_upstart(svc_action_t *op)
+{
+ char *job = NULL;
+ int arg_wait = TRUE;
+ const char *arg_env = "pacemaker=1";
+ const char *action = op->action;
+
+ DBusError error;
+ DBusMessage *msg = NULL;
+ DBusMessage *reply = NULL;
+ DBusMessageIter iter, array_iter;
+
+ CRM_ASSERT(op != NULL);
+
+ if ((op->action == NULL) || (op->agent == NULL)) {
+ services__set_result(op, PCMK_OCF_NOT_CONFIGURED, PCMK_EXEC_ERROR_FATAL,
+ "Bug in action caller");
+ goto cleanup;
+ }
+
+ if (!upstart_init()) {
+ services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "No DBus connection");
+ goto cleanup;
+ }
+
+ if (pcmk__str_eq(op->action, "meta-data", pcmk__str_casei)) {
+ op->stdout_data = upstart_job_metadata(op->agent);
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+ goto cleanup;
+ }
+
+ if (!object_path_for_job(op->agent, &job, op->timeout)) {
+ if (pcmk__str_eq(action, "stop", pcmk__str_none)) {
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+ } else {
+ services__set_result(op, PCMK_OCF_NOT_INSTALLED,
+ PCMK_EXEC_NOT_INSTALLED,
+ "Upstart job not found");
+ }
+ goto cleanup;
+ }
+
+ if (job == NULL) {
+ // Shouldn't normally be possible -- maybe a memory error
+ op->rc = PCMK_OCF_UNKNOWN_ERROR;
+ op->status = PCMK_EXEC_ERROR;
+ goto cleanup;
+ }
+
+ if (pcmk__strcase_any_of(op->action, "monitor", "status", NULL)) {
+ DBusPendingCall *pending = NULL;
+ char *state = NULL;
+ char *path = get_first_instance(job, op->timeout);
+
+ services__set_result(op, PCMK_OCF_NOT_RUNNING, PCMK_EXEC_DONE,
+ "No Upstart job instances found");
+ if (path == NULL) {
+ goto cleanup;
+ }
+ state = pcmk_dbus_get_property(upstart_proxy, BUS_NAME, path,
+ UPSTART_06_API ".Instance", "state",
+ op->synchronous? NULL : parse_status_result,
+ op,
+ op->synchronous? NULL : &pending,
+ op->timeout);
+ free(path);
+
+ if (op->synchronous) {
+ parse_status_result("state", state, op);
+ free(state);
+
+ } else if (pending == NULL) {
+ services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "Could not get job state from DBus");
+
+ } else { // Successfully initiated async op
+ free(job);
+ services_set_op_pending(op, pending);
+ services_add_inflight_op(op);
+ return pcmk_rc_ok;
+ }
+
+ goto cleanup;
+
+ } else if (pcmk__str_eq(action, "start", pcmk__str_none)) {
+ action = "Start";
+
+ } else if (pcmk__str_eq(action, "stop", pcmk__str_none)) {
+ action = "Stop";
+
+ } else if (pcmk__str_eq(action, "restart", pcmk__str_none)) {
+ action = "Restart";
+
+ } else {
+ services__set_result(op, PCMK_OCF_UNIMPLEMENT_FEATURE,
+ PCMK_EXEC_ERROR_HARD,
+ "Action not implemented for Upstart resources");
+ goto cleanup;
+ }
+
+ // Initialize rc/status in case called functions don't set them
+ services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_DONE,
+ "Bug in service library");
+
+ crm_debug("Calling %s for %s on %s",
+ action, pcmk__s(op->rsc, "unknown resource"), job);
+
+ msg = dbus_message_new_method_call(BUS_NAME, // target for the method call
+ job, // object to call on
+ UPSTART_JOB_IFACE, // interface to call on
+ action); // method name
+ CRM_ASSERT(msg != NULL);
+
+ dbus_message_iter_init_append (msg, &iter);
+ CRM_LOG_ASSERT(dbus_message_iter_open_container(&iter,
+ DBUS_TYPE_ARRAY,
+ DBUS_TYPE_STRING_AS_STRING,
+ &array_iter));
+ CRM_LOG_ASSERT(dbus_message_iter_append_basic(&array_iter,
+ DBUS_TYPE_STRING, &arg_env));
+ CRM_LOG_ASSERT(dbus_message_iter_close_container(&iter, &array_iter));
+ CRM_LOG_ASSERT(dbus_message_append_args(msg, DBUS_TYPE_BOOLEAN, &arg_wait,
+ DBUS_TYPE_INVALID));
+
+ if (!(op->synchronous)) {
+ DBusPendingCall *pending = pcmk_dbus_send(msg, upstart_proxy,
+ job_method_complete, op,
+ op->timeout);
+
+ if (pending == NULL) {
+ services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
+ "Unable to send DBus message");
+ goto cleanup;
+
+ } else { // Successfully initiated async op
+ free(job);
+ services_set_op_pending(op, pending);
+ services_add_inflight_op(op);
+ return pcmk_rc_ok;
+ }
+ }
+
+ // Synchronous call
+
+ dbus_error_init(&error);
+ reply = pcmk_dbus_send_recv(msg, upstart_proxy, &error, op->timeout);
+
+ if (dbus_error_is_set(&error)) {
+ set_result_from_method_error(op, &error);
+ dbus_error_free(&error);
+
+ } else if (pcmk__str_eq(op->action, "stop", pcmk__str_none)) {
+ // DBus call does not return a value
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+
+ } else if (!pcmk_dbus_type_check(reply, NULL, DBUS_TYPE_OBJECT_PATH,
+ __func__, __LINE__)) {
+ crm_info("Call to %s passed but return type was unexpected",
+ op->action);
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+
+ } else {
+ const char *path = NULL;
+
+ dbus_message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID);
+ crm_debug("Call to %s passed: %s", op->action, path);
+ services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
+ }
+
+cleanup:
+ free(job);
+ if (msg != NULL) {
+ dbus_message_unref(msg);
+ }
+ if (reply != NULL) {
+ dbus_message_unref(reply);
+ }
+
+ if (op->synchronous) {
+ return (op->rc == PCMK_OCF_OK)? pcmk_rc_ok : pcmk_rc_error;
+ } else {
+ return services__finalize_async_op(op);
+ }
+}
diff --git a/lib/services/upstart.h b/lib/services/upstart.h
new file mode 100644
index 0000000..b6c4eff
--- /dev/null
+++ b/lib/services/upstart.h
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010 Senko Rasic <senko.rasic@dobarkod.hr>
+ * Copyright 2010 Ante Karamatic <ivoks@init.hr>
+ * 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 UPSTART__H
+# define UPSTART__H
+
+# include <glib.h>
+# include "crm/services.h"
+
+G_GNUC_INTERNAL GList *upstart_job_listall(void);
+
+G_GNUC_INTERNAL
+int services__upstart_prepare(svc_action_t *op);
+
+G_GNUC_INTERNAL
+enum ocf_exitcode services__upstart2ocf(int exit_status);
+
+G_GNUC_INTERNAL
+int services__execute_upstart(svc_action_t *op);
+
+G_GNUC_INTERNAL gboolean upstart_job_exists(const gchar * name);
+G_GNUC_INTERNAL void upstart_cleanup(void);
+
+#endif /* UPSTART__H */