Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/lib/services/systemd.c b/lib/services/systemd.c
index 64cfae0971..3b513a7c29 100644
--- a/lib/services/systemd.c
+++ b/lib/services/systemd.c
@@ -1,1415 +1,1432 @@
/*
* Copyright 2012-2025 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/common/xml.h>
#include <crm/services.h>
#include <crm/services_internal.h>
#include <crm/common/mainloop.h>
#include <inttypes.h> // PRIu32
#include <stdbool.h>
#include <stdint.h> // uint32_t
#include <stdio.h> // fopen(), NULL, etc.
#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);
/* Systemd D-Bus interface
* https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.systemd1.html
*/
#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 = NULL;
DBusMessage *reply = NULL;
DBusError error;
/* Don't call systemd_init() here, because that calls this */
CRM_CHECK(systemd_proxy, return NULL);
msg = systemd_new_method(method);
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;
}
/*!
* \internal
* \brief Subscribe to D-Bus signals from systemd
*
* Systemd does not broadcast signal messages unless at least one client has
* called the \c Subscribe() method. Also, a D-Bus client ignores broadcast
* messages unless an appropriate match rule is set, so we set one here.
*
* \return Standard Pacemaker return code
*/
static int
subscribe_to_signals(void)
{
const char *match_rule = "type='signal',"
"sender='" BUS_NAME "',"
"interface='" BUS_NAME_MANAGER "',"
"path='" BUS_PATH "'";
DBusMessage *reply = NULL;
DBusError error;
/* Tell D-Bus to accept signal messages from systemd.
* https://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules
*/
dbus_error_init(&error);
dbus_bus_add_match(systemd_proxy, match_rule, &error);
if (dbus_error_is_set(&error)) {
crm_err("Could not listen for systemd DBus signals: %s " QB_XS " (%s)",
error.message, error.name);
dbus_error_free(&error);
return ECOMM;
}
// Tell systemd to broadcast signals
reply = systemd_call_simple_method("Subscribe");
if (reply == NULL) {
dbus_bus_remove_match(systemd_proxy, match_rule, &error);
return ECOMM;
}
dbus_message_unref(reply);
return pcmk_rc_ok;
}
static bool
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 (subscribe_to_signals() != pcmk_rc_ok) {
pcmk_dbus_disconnect(systemd_proxy);
systemd_proxy = NULL;
}
}
return (systemd_proxy != NULL);
}
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_unit_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) == '@') {
return crm_strdup_printf("%.*spacemaker%s",
(int) (dot - name), name, dot);
} else {
return pcmk__str_copy(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++;
pcmk__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, PCMK_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);
+
+ /* If systemd happens to be re-executing by `systemctl daemon-reexec` at the
+ * same time, dbus gives an error with the name
+ * `org.freedesktop.DBus.Error.NoReply` and the message "Message recipient
+ * disconnected from message bus without replying".
+ * Consider the monitor pending rather than return an error yet, so that it
+ * can retry with another iteration.
+ */
+ } else if (pcmk__str_any_of(op->action, PCMK_ACTION_MONITOR,
+ PCMK_ACTION_STATUS, NULL)
+ && strstr(error->name, DBUS_ERROR_NO_REPLY)
+ && strstr(error->message, "disconnected")) {
+ services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
}
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);
+ if (!pcmk__str_any_of(op->action, PCMK_ACTION_MONITOR,
+ PCMK_ACTION_STATUS, NULL)
+ || op->status != PCMK_EXEC_PENDING) {
+ 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 (pcmk__str_empty(arg_name)) {
return EINVAL;
}
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");
pcmk__assert(msg != NULL);
// Add the (expanded) unit name as the argument
name = systemd_unit_name(arg_name,
(op == NULL)
|| pcmk__str_eq(op->action, PCMK_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()) {
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;
}
bool
systemd_unit_exists(const char *name)
{
char *path = NULL;
char *state = NULL;
int rc = false;
/* 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)) {
goto done;
}
/* 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);
rc = pcmk__str_any_of(state, "loaded", "masked", NULL);
done:
free(path);
free(state);
return rc;
}
// @TODO Use XML string constants and maybe a real XML object
#define METADATA_FORMAT \
"<?xml " PCMK_XA_VERSION "=\"1.0\"?>\n" \
"<" PCMK_XE_RESOURCE_AGENT " " \
PCMK_XA_NAME "=\"%s\" " \
PCMK_XA_VERSION "=\"" PCMK_DEFAULT_AGENT_VERSION "\">\n" \
" <" PCMK_XE_VERSION ">1.1</" PCMK_XE_VERSION ">\n" \
" <" PCMK_XE_LONGDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">\n" \
" %s\n" \
" </" PCMK_XE_LONGDESC ">\n" \
" <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">" \
"systemd unit file for %s" \
"</" PCMK_XE_SHORTDESC ">\n" \
" <" PCMK_XE_PARAMETERS "/>\n" \
" <" PCMK_XE_ACTIONS ">\n" \
" <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_START "\"" \
" " PCMK_META_TIMEOUT "=\"100s\" />\n" \
" <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_STOP "\"" \
" " PCMK_META_TIMEOUT "=\"100s\" />\n" \
" <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_STATUS "\"" \
" " PCMK_META_TIMEOUT "=\"100s\" />\n" \
" <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_MONITOR "\"" \
" " PCMK_META_TIMEOUT "=\"100s\"" \
" " PCMK_META_INTERVAL "=\"60s\" />\n" \
" <" PCMK_XE_ACTION " " PCMK_XA_NAME "=\"" PCMK_ACTION_META_DATA "\"" \
" " PCMK_META_TIMEOUT "=\"5s\" />\n" \
" </" PCMK_XE_ACTIONS ">\n" \
" <" PCMK_XE_SPECIAL " " PCMK_XA_TAG "=\"systemd\"/>\n" \
"</" PCMK_XE_RESOURCE_AGENT ">\n"
static char *
systemd_unit_metadata(const char *name, int timeout)
{
char *meta = NULL;
char *desc = NULL;
char *path = 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);
}
if (pcmk__xml_needs_escape(desc, pcmk__xml_escape_text)) {
gchar *escaped = pcmk__xml_escape(desc, pcmk__xml_escape_text);
meta = crm_strdup_printf(METADATA_FORMAT, name, escaped, name);
g_free(escaped);
} else {
meta = crm_strdup_printf(METADATA_FORMAT, name, desc, name);
}
free(desc);
free(path);
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)
{
bool start_stop = pcmk__strcase_any_of(op->action, PCMK_ACTION_START,
PCMK_ACTION_STOP, NULL);
DBusError error;
dbus_error_init(&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__)) {
const char *reason = "systemd D-Bus method had unexpected reply";
crm_info("DBus request for %s of %s succeeded but "
"return type was unexpected",
op->action, pcmk__s(op->rsc, "unknown resource"));
if (!op->synchronous && start_stop) {
/* The start or stop job is enqueued but is not complete. We need a
* job path to detect completion in job_removed_filter().
*/
services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
reason);
} else {
/* Something weird happened, but the action is finished and there
* was no D-Bus error. So call it a success.
*/
services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, reason);
}
} 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);
if (!op->synchronous && start_stop) {
// Should be set to unknown/pending already
services__set_result(op, PCMK_OCF_UNKNOWN, PCMK_EXEC_PENDING, NULL);
pcmk__str_update(&(op->opaque->job_path), path);
} else {
services__set_result(op, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
}
}
}
/*!
* \internal
* \brief Process a systemd \c JobRemoved signal for a given service action
*
* This filter is expected to be added with \c finalize_async_action_dbus() as
* the \c free_data_function. Then if \p message is a \c JobRemoved signal for
* the action specified by \p user_data, the action's result is set, the filter
* is removed, and the action is finalized.
*
* \param[in,out] connection D-Bus connection
* \param[in] message D-Bus message
* \param[in,out] user_data Service action (\c svc_action_t)
*
* \retval \c DBUS_HANDLER_RESULT_HANDLED if \p message is a \c JobRemoved
* signal for \p user_data
* \retval \c DBUS_HANDLER_RESULT_NOT_YET_HANDLED otherwise (on error, if
* \p message is not a \c JobRemoved signal, or if the signal is for
* some other action's job)
*/
static DBusHandlerResult
job_removed_filter(DBusConnection *connection, DBusMessage *message,
void *user_data)
{
svc_action_t *action = user_data;
const char *action_name = NULL;
uint32_t job_id = 0;
const char *bus_path = NULL;
const char *unit_name = NULL;
const char *result = NULL;
DBusError error;
CRM_CHECK((connection != NULL) && (message != NULL),
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED);
// action should always be set when the filter is added
if ((action == NULL)
|| !dbus_message_is_signal(message, BUS_NAME_MANAGER, "JobRemoved")) {
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
dbus_error_init(&error);
if (!dbus_message_get_args(message, &error,
DBUS_TYPE_UINT32, &job_id,
DBUS_TYPE_OBJECT_PATH, &bus_path,
DBUS_TYPE_STRING, &unit_name,
DBUS_TYPE_STRING, &result,
DBUS_TYPE_INVALID)) {
crm_err("Could not interpret systemd DBus signal: %s " QB_XS " (%s)",
error.message, error.name);
dbus_error_free(&error);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
if (!pcmk__str_eq(bus_path, action->opaque->job_path, pcmk__str_none)) {
// This filter is not for this job
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
action_name = pcmk__s(action->action, "(unknown)");
crm_trace("Setting %s result for %s (JobRemoved id=%" PRIu32 ", result=%s",
action_name, unit_name, job_id, result);
if (pcmk__str_eq(result, "done", pcmk__str_none)) {
services__set_result(action, PCMK_OCF_OK, PCMK_EXEC_DONE, NULL);
} else if (pcmk__str_eq(result, "timeout", pcmk__str_none)) {
services__format_result(action, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_TIMEOUT,
"systemd %s job for %s timed out",
action_name, unit_name);
} else {
services__format_result(action, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
"systemd %s job for %s failed with result '%s'",
action_name, unit_name, result);
}
/* This instance of the filter was specifically for the given action.
*
* The action gets finalized by services__finalize_async_op() via the
* filter's free_data_function.
*/
dbus_connection_remove_filter(systemd_proxy, job_removed_filter, action);
return DBUS_HANDLER_RESULT_HANDLED;
}
/*!
* \internal
* \brief \c DBusFreeFunction wrapper for \c services__finalize_async_op()
*
* \param[in,out] action Asynchronous service action to finalize
*/
static void
finalize_async_action_dbus(void *action)
{
services__finalize_async_op((svc_action_t *) action);
}
/*!
* \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);
process_unit_method_reply(reply, op);
if (reply != NULL) {
dbus_message_unref(reply);
}
if ((op->status == PCMK_EXEC_PENDING)
&& pcmk__strcase_any_of(op->action, PCMK_ACTION_START, PCMK_ACTION_STOP,
NULL)) {
/* Start and stop method calls return when the job is enqueued, not when
* it's complete. Start and stop actions must be finalized after the job
* is complete, because the action callback function may use it. We add
* a message filter to process the JobRemoved signal, which indicates
* completion.
*
* The filter takes ownership of op, which will be finalized when the
* filter is later removed.
*/
if (dbus_connection_add_filter(systemd_proxy, job_removed_filter, op,
finalize_async_action_dbus)) {
return;
}
crm_err("Could not add D-Bus filter for systemd JobRemoved signals");
services__set_result(op, PCMK_OCF_UNKNOWN_ERROR, PCMK_EXEC_ERROR,
"Failed to add D-Bus filter for systemd "
"JobRemoved signal");
}
services__finalize_async_op(op);
}
/* 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_UNIT_OVERRIDE_TEMPLATE \
"[Unit]\n" \
"Description=Cluster Controlled %s\n" \
"Before=pacemaker.service pacemaker_remote.service\n"
#define SYSTEMD_SERVICE_OVERRIDE \
"\n" \
"[Service]\n" \
"Restart=no\n"
/*!
* \internal
* \brief Get runtime drop-in directory path for a systemd unit
*
* \param[in] unit_name Systemd unit (with extension)
*
* \return Drop-in directory path
*/
static GString *
get_override_dir(const char *unit_name)
{
GString *buf = g_string_sized_new(128);
pcmk__g_strcat(buf, "/run/systemd/system/", unit_name, ".d", NULL);
return buf;
}
/*!
* \internal
* \brief Append systemd override filename to a directory path
*
* \param[in,out] buf Buffer containing directory path to append to
*/
static inline void
append_override_basename(GString *buf)
{
g_string_append(buf, "/50-pacemaker.conf");
}
/*!
* \internal
* \brief Create a runtime override file for a systemd unit
*
* The systemd daemon is then reloaded. This file does not survive a reboot.
*
* \param[in] agent Systemd resource agent
* \param[in] timeout Timeout for systemd daemon reload
*
* \return Standard Pacemaker return code
*
* \note Any configuration in \c /etc takes precedence over our drop-in.
* \todo Document this in Pacemaker Explained or Administration?
*/
static int
systemd_create_override(const char *agent, int timeout)
{
char *unit_name = NULL;
GString *filename = NULL;
GString *override = NULL;
FILE *fp = NULL;
int fd = 0;
int rc = pcmk_rc_ok;
unit_name = systemd_unit_name(agent, false);
CRM_CHECK(!pcmk__str_empty(unit_name),
rc = EINVAL; goto done);
filename = get_override_dir(unit_name);
rc = pcmk__build_path(filename->str, 0755);
if (rc != pcmk_rc_ok) {
crm_err("Could not create systemd override directory %s: %s",
filename->str, pcmk_rc_str(rc));
goto done;
}
append_override_basename(filename);
fp = fopen(filename->str, "w");
if (fp == NULL) {
rc = errno;
crm_err("Cannot open systemd override file %s for writing: %s",
filename->str, pcmk_rc_str(rc));
goto done;
}
// Ensure the override file is world-readable (avoid systemd warning in log)
fd = fileno(fp);
if ((fd < 0) || (fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH) < 0)) {
rc = errno;
crm_err("Failed to set permissions on systemd override file %s: %s",
filename->str, pcmk_rc_str(rc));
goto done;
}
override = g_string_sized_new(2 * sizeof(SYSTEMD_UNIT_OVERRIDE_TEMPLATE));
g_string_printf(override, SYSTEMD_UNIT_OVERRIDE_TEMPLATE, unit_name);
if (pcmk__ends_with_ext(unit_name, ".service")) {
g_string_append(override, SYSTEMD_SERVICE_OVERRIDE);
}
if (fputs(override->str, fp) == EOF) {
rc = EIO;
crm_err("Cannot write to systemd override file %s", filename->str);
}
done:
if (fp != NULL) {
fclose(fp);
}
if (rc == pcmk_rc_ok) {
// @TODO Make sure the reload succeeds
systemd_daemon_reload(timeout);
} else if (fp != NULL) {
// File was created, so remove it
unlink(filename->str);
}
free(unit_name);
// coverity[check_after_deref : FALSE]
if (filename != NULL) {
g_string_free(filename, TRUE);
}
if (override != NULL) {
g_string_free(override, TRUE);
}
return rc;
}
static void
systemd_remove_override(const char *agent, int timeout)
{
char *unit_name = systemd_unit_name(agent, false);
GString *filename = NULL;
CRM_CHECK(!pcmk__str_empty(unit_name), goto done);
filename = get_override_dir(unit_name);
append_override_basename(filename);
if (unlink(filename->str) < 0) {
int rc = errno;
if (rc != ENOENT) {
// Stop may be called when already stopped, which is fine
crm_warn("Cannot remove systemd override file %s: %s",
filename->str, pcmk_rc_str(rc));
}
} else {
systemd_daemon_reload(timeout);
}
done:
free(unit_name);
// coverity[check_after_deref : FALSE]
if (filename != NULL) {
g_string_free(filename, TRUE);
}
}
/*!
* \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, PCMK_ACTION_MONITOR, PCMK_ACTION_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, PCMK_ACTION_START, pcmk__str_none)) {
int rc = pcmk_rc_ok;
method = "StartUnit";
rc = systemd_create_override(op->agent, op->timeout);
if (rc != pcmk_rc_ok) {
services__format_result(op, pcmk_rc2ocf(rc), PCMK_EXEC_ERROR,
"Failed to create systemd override file "
"for %s",
pcmk__s(op->agent, "(unspecified)"));
if (!(op->synchronous)) {
services__finalize_async_op(op);
}
return;
}
} else if (pcmk__str_eq(op->action, PCMK_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);
pcmk__assert(msg != NULL);
/* (ss) */
{
const char *replace_s = "replace";
char *name = systemd_unit_name(op->agent,
pcmk__str_eq(op->action,
PCMK_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);
if (op->opaque->job_path != NULL) {
// A filter owns this op
dbus_connection_remove_filter(systemd_proxy, job_removed_filter, op);
} else {
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)
{
pcmk__assert(op != NULL);
if (pcmk__str_empty(op->action) || pcmk__str_empty(op->agent)) {
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, PCMK_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) {
// @TODO Why plus 5000? No explanation in fccd046.
op->opaque->timerid = pcmk__create_timer(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);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Wed, Jun 25, 6:19 AM (21 h, 47 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1950632
Default Alt Text
(47 KB)

Event Timeline