Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/options_internal.h b/include/crm/common/options_internal.h
index 3395ce5abc..c76cc2042f 100644
--- a/include/crm/common/options_internal.h
+++ b/include/crm/common/options_internal.h
@@ -1,184 +1,185 @@
/*
* Copyright 2006-2024 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__OPTIONS_INTERNAL__H
# define PCMK__OPTIONS_INTERNAL__H
# ifndef PCMK__CONFIG_H
# define PCMK__CONFIG_H
# include <config.h> // _Noreturn
# endif
# include <glib.h> // GHashTable
# include <stdbool.h> // bool
#include <crm/common/util.h> // pcmk_parse_interval_spec()
_Noreturn void pcmk__cli_help(char cmd);
/*
* Environment variable option handling
*/
const char *pcmk__env_option(const char *option);
void pcmk__set_env_option(const char *option, const char *value, bool compat);
bool pcmk__env_option_enabled(const char *daemon, const char *option);
/*
* Cluster option handling
*/
typedef struct pcmk__cluster_option_s {
const char *name;
const char *alt_name;
const char *type;
const char *values;
const char *default_value;
bool (*is_valid)(const char *);
const char *description_short;
const char *description_long;
} pcmk__cluster_option_t;
const char *pcmk__cluster_option(GHashTable *options,
const pcmk__cluster_option_t *option_list,
int len, const char *name);
gchar *pcmk__format_option_metadata(const char *name, const char *desc_short,
const char *desc_long,
pcmk__cluster_option_t *option_list,
int len);
void pcmk__validate_cluster_options(GHashTable *options,
pcmk__cluster_option_t *option_list,
int len);
bool pcmk__valid_interval_spec(const char *value);
bool pcmk__valid_boolean(const char *value);
bool pcmk__valid_int(const char *value);
bool pcmk__valid_positive_int(const char *value);
bool pcmk__valid_no_quorum_policy(const char *value);
bool pcmk__valid_percentage(const char *value);
bool pcmk__valid_script(const char *value);
// from watchdog.c
long pcmk__get_sbd_watchdog_timeout(void);
bool pcmk__get_sbd_sync_resource_startup(void);
long pcmk__auto_stonith_watchdog_timeout(void);
bool pcmk__valid_stonith_watchdog_timeout(const char *value);
// Constants for environment variable names
#define PCMK__ENV_AUTHKEY_LOCATION "authkey_location"
#define PCMK__ENV_BLACKBOX "blackbox"
#define PCMK__ENV_CALLGRIND_ENABLED "callgrind_enabled"
#define PCMK__ENV_CLUSTER_TYPE "cluster_type"
#define PCMK__ENV_DEBUG "debug"
#define PCMK__ENV_DH_MAX_BITS "dh_max_bits"
#define PCMK__ENV_DH_MIN_BITS "dh_min_bits"
#define PCMK__ENV_FAIL_FAST "fail_fast"
#define PCMK__ENV_IPC_BUFFER "ipc_buffer"
#define PCMK__ENV_IPC_TYPE "ipc_type"
#define PCMK__ENV_LOGFACILITY "logfacility"
#define PCMK__ENV_LOGFILE "logfile"
#define PCMK__ENV_LOGFILE_MODE "logfile_mode"
#define PCMK__ENV_LOGPRIORITY "logpriority"
#define PCMK__ENV_NODE_ACTION_LIMIT "node_action_limit"
#define PCMK__ENV_NODE_START_STATE "node_start_state"
#define PCMK__ENV_PANIC_ACTION "panic_action"
#define PCMK__ENV_REMOTE_ADDRESS "remote_address"
#define PCMK__ENV_REMOTE_SCHEMA_DIR "remote_schema_directory"
#define PCMK__ENV_REMOTE_PID1 "remote_pid1"
#define PCMK__ENV_REMOTE_PORT "remote_port"
#define PCMK__ENV_RESPAWNED "respawned"
#define PCMK__ENV_SCHEMA_DIRECTORY "schema_directory"
#define PCMK__ENV_SERVICE "service"
#define PCMK__ENV_STDERR "stderr"
#define PCMK__ENV_TLS_PRIORITIES "tls_priorities"
#define PCMK__ENV_TRACE_BLACKBOX "trace_blackbox"
#define PCMK__ENV_TRACE_FILES "trace_files"
#define PCMK__ENV_TRACE_FORMATS "trace_formats"
#define PCMK__ENV_TRACE_FUNCTIONS "trace_functions"
#define PCMK__ENV_TRACE_TAGS "trace_tags"
#define PCMK__ENV_VALGRIND_ENABLED "valgrind_enabled"
// @COMPAT Drop at 3.0.0; default is plenty
#define PCMK__ENV_CIB_TIMEOUT "cib_timeout"
// @COMPAT Drop at 3.0.0; likely last used in 1.1.24
#define PCMK__ENV_MCP "mcp"
// @COMPAT Drop at 3.0.0; added unused in 1.1.9
#define PCMK__ENV_QUORUM_TYPE "quorum_type"
/* @COMPAT Drop at 3.0.0; added to debug shutdown issues when Pacemaker is
* managed by systemd, but no longer useful.
*/
#define PCMK__ENV_SHUTDOWN_DELAY "shutdown_delay"
// @COMPAT Deprecated since 2.1.0
#define PCMK__OPT_REMOVE_AFTER_STOP "remove-after-stop"
// Constants for meta-attribute names
#define PCMK__META_CLONE_INSTANCE_NUM "clone"
#define PCMK__META_CONTAINER "container"
#define PCMK__META_DIGESTS_ALL "digests-all"
#define PCMK__META_DIGESTS_SECURE "digests-secure"
#define PCMK__META_INTERNAL_RSC "internal_rsc"
#define PCMK__META_MIGRATE_SOURCE "migrate_source"
#define PCMK__META_MIGRATE_TARGET "migrate_target"
#define PCMK__META_ON_NODE "on_node"
#define PCMK__META_ON_NODE_UUID "on_node_uuid"
#define PCMK__META_OP_NO_WAIT "op_no_wait"
#define PCMK__META_OP_TARGET_RC "op_target_rc"
#define PCMK__META_PHYSICAL_HOST "physical-host"
/* @TODO Plug these in. Currently, they're never set. These are op attrs for use
* with https://projects.clusterlabs.org/T382.
*/
#define PCMK__META_CLEAR_FAILURE_OP "clear_failure_op"
#define PCMK__META_CLEAR_FAILURE_INTERVAL "clear_failure_interval"
// @COMPAT Deprecated meta-attribute since 2.1.0
#define PCMK__META_CAN_FAIL "can_fail"
// @COMPAT Deprecated alias for PCMK__META_PROMOTED_MAX since 2.0.0
#define PCMK__META_PROMOTED_MAX_LEGACY "master-max"
// @COMPAT Deprecated alias for PCMK__META_PROMOTED_NODE_MAX since 2.0.0
#define PCMK__META_PROMOTED_NODE_MAX_LEGACY "master-node-max"
// @COMPAT Deprecated meta-attribute since 2.0.0
#define PCMK__META_RESTART_TYPE "restart-type"
// @COMPAT Deprecated meta-attribute since 2.0.0
#define PCMK__META_ROLE_AFTER_FAILURE "role_after_failure"
// Constants for enumerated values for various options
#define PCMK__VALUE_CLUSTER "cluster"
#define PCMK__VALUE_CUSTOM "custom"
+#define PCMK__VALUE_EN "en"
#define PCMK__VALUE_FENCING "fencing"
#define PCMK__VALUE_GREEN "green"
#define PCMK__VALUE_LOCAL "local"
#define PCMK__VALUE_MIGRATE_ON_RED "migrate-on-red"
#define PCMK__VALUE_NONE "none"
#define PCMK__VALUE_NOTHING "nothing"
#define PCMK__VALUE_ONLY_GREEN "only-green"
#define PCMK__VALUE_PROGRESSIVE "progressive"
#define PCMK__VALUE_QUORUM "quorum"
#define PCMK__VALUE_RED "red"
#define PCMK__VALUE_REQUEST "request"
#define PCMK__VALUE_RESPONSE "response"
#define PCMK__VALUE_UNFENCING "unfencing"
#define PCMK__VALUE_YELLOW "yellow"
#endif // PCMK__OPTIONS_INTERNAL__H
diff --git a/lib/common/options.c b/lib/common/options.c
index 38a823c69a..62fcc8ca7b 100644
--- a/lib/common/options.c
+++ b/lib/common/options.c
@@ -1,590 +1,592 @@
/*
* Copyright 2004-2024 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 _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <crm_internal.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
void
pcmk__cli_help(char cmd)
{
if (cmd == 'v' || cmd == '$') {
printf("Pacemaker %s\n", PACEMAKER_VERSION);
printf("Written by Andrew Beekhof and "
"the Pacemaker project contributors\n");
} else if (cmd == '!') {
printf("Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
}
crm_exit(CRM_EX_OK);
while(1); // above does not return
}
/*
* Environment variable option handling
*/
/*!
* \internal
* \brief Get the value of a Pacemaker environment variable option
*
* If an environment variable option is set, with either a PCMK_ or (for
* backward compatibility) HA_ prefix, log and return the value.
*
* \param[in] option Environment variable name (without prefix)
*
* \return Value of environment variable option, or NULL in case of
* option name too long or value not found
*/
const char *
pcmk__env_option(const char *option)
{
const char *const prefixes[] = {"PCMK_", "HA_"};
char env_name[NAME_MAX];
const char *value = NULL;
CRM_CHECK(!pcmk__str_empty(option), return NULL);
for (int i = 0; i < PCMK__NELEM(prefixes); i++) {
int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option);
if (rv < 0) {
crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option,
strerror(errno));
return NULL;
}
if (rv >= sizeof(env_name)) {
crm_trace("\"%s%s\" is too long", prefixes[i], option);
continue;
}
value = getenv(env_name);
if (value != NULL) {
crm_trace("Found %s = %s", env_name, value);
return value;
}
}
crm_trace("Nothing found for %s", option);
return NULL;
}
/*!
* \brief Set or unset a Pacemaker environment variable option
*
* Set an environment variable option with a \c "PCMK_" prefix and optionally
* an \c "HA_" prefix for backward compatibility.
*
* \param[in] option Environment variable name (without prefix)
* \param[in] value New value (or NULL to unset)
* \param[in] compat If false and \p value is not \c NULL, set only
* \c "PCMK_<option>"; otherwise, set (or unset) both
* \c "PCMK_<option>" and \c "HA_<option>"
*
* \note \p compat is ignored when \p value is \c NULL. A \c NULL \p value
* means we're unsetting \p option. \c pcmk__get_env_option() checks for
* both prefixes, so we want to clear them both.
*/
void
pcmk__set_env_option(const char *option, const char *value, bool compat)
{
// @COMPAT Drop support for "HA_" options eventually
const char *const prefixes[] = {"PCMK_", "HA_"};
char env_name[NAME_MAX];
CRM_CHECK(!pcmk__str_empty(option) && (strchr(option, '=') == NULL),
return);
for (int i = 0; i < PCMK__NELEM(prefixes); i++) {
int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option);
if (rv < 0) {
crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option,
strerror(errno));
return;
}
if (rv >= sizeof(env_name)) {
crm_trace("\"%s%s\" is too long", prefixes[i], option);
continue;
}
if (value != NULL) {
crm_trace("Setting %s to %s", env_name, value);
rv = setenv(env_name, value, 1);
} else {
crm_trace("Unsetting %s", env_name);
rv = unsetenv(env_name);
}
if (rv < 0) {
crm_err("Failed to %sset %s: %s", (value != NULL)? "" : "un",
env_name, strerror(errno));
}
if (!compat && (value != NULL)) {
// For set, don't proceed to HA_<option> unless compat is enabled
break;
}
}
}
/*!
* \internal
* \brief Check whether Pacemaker environment variable option is enabled
*
* Given a Pacemaker environment variable option that can either be boolean
* or a list of daemon names, return true if the option is enabled for a given
* daemon.
*
* \param[in] daemon Daemon name (can be NULL)
* \param[in] option Pacemaker environment variable name
*
* \return true if variable is enabled for daemon, otherwise false
*/
bool
pcmk__env_option_enabled(const char *daemon, const char *option)
{
const char *value = pcmk__env_option(option);
return (value != NULL)
&& (crm_is_true(value)
|| ((daemon != NULL) && (strstr(value, daemon) != NULL)));
}
/*
* Cluster option handling
*/
/*!
* \internal
* \brief Check whether a string represents a valid interval specification
*
* \param[in] value String to validate
*
* \return \c true if \p value is a valid interval specification, or \c false
* otherwise
*/
bool
pcmk__valid_interval_spec(const char *value)
{
return pcmk_parse_interval_spec(value, NULL) == pcmk_rc_ok;
}
/*!
* \internal
* \brief Check whether a string represents a valid boolean value
*
* \param[in] value String to validate
*
* \return \c true if \p value is a valid boolean value, or \c false otherwise
*/
bool
pcmk__valid_boolean(const char *value)
{
return crm_str_to_boolean(value, NULL) == 1;
}
/*!
* \internal
* \brief Check whether a string represents a valid integer
*
* Valid values include \c INFINITY, \c -INFINITY, and all 64-bit integers.
*
* \param[in] value String to validate
*
* \return \c true if \p value is a valid integer, or \c false otherwise
*/
bool
pcmk__valid_int(const char *value)
{
return (value != NULL)
&& (pcmk_str_is_infinity(value)
|| pcmk_str_is_minus_infinity(value)
|| (pcmk__scan_ll(value, NULL, 0LL) == pcmk_rc_ok));
}
/*!
* \internal
* \brief Check whether a string represents a valid positive integer
*
* Valid values include \c INFINITY and all 64-bit positive integers.
*
* \param[in] value String to validate
*
* \return \c true if \p value is a valid positive integer, or \c false
* otherwise
*/
bool
pcmk__valid_positive_int(const char *value)
{
long long num = 0LL;
return pcmk_str_is_infinity(value)
|| ((pcmk__scan_ll(value, &num, 0LL) == pcmk_rc_ok)
&& (num > 0));
}
/*!
* \internal
* \brief Check whether a string represents a valid
* \c PCMK__OPT_NO_QUORUM_POLICY value
*
* \param[in] value String to validate
*
* \return \c true if \p value is a valid \c PCMK__OPT_NO_QUORUM_POLICY value,
* or \c false otherwise
*/
bool
pcmk__valid_no_quorum_policy(const char *value)
{
return pcmk__strcase_any_of(value,
"stop", "freeze", "ignore", "demote", "suicide",
NULL);
}
/*!
* \internal
* \brief Check whether a string represents a valid percentage
*
* Valid values include long integers, with an optional trailing string
* beginning with '%'.
*
* \param[in] value String to validate
*
* \return \c true if \p value is a valid percentage value, or \c false
* otherwise
*/
bool
pcmk__valid_percentage(const char *value)
{
char *end = NULL;
float number = strtof(value, &end);
return ((end == NULL) || (end[0] == '%')) && (number >= 0);
}
/*!
* \internal
* \brief Check whether a string represents a valid script
*
* Valid values include \c /dev/null and paths of executable regular files
*
* \param[in] value String to validate
*
* \return \c true if \p value is a valid script, or \c false otherwise
*/
bool
pcmk__valid_script(const char *value)
{
struct stat st;
if (pcmk__str_eq(value, "/dev/null", pcmk__str_none)) {
return true;
}
if (stat(value, &st) != 0) {
crm_err("Script %s does not exist", value);
return false;
}
if (S_ISREG(st.st_mode) == 0) {
crm_err("Script %s is not a regular file", value);
return false;
}
if ((st.st_mode & (S_IXUSR | S_IXGRP)) == 0) {
crm_err("Script %s is not executable", value);
return false;
}
return true;
}
/*!
* \internal
* \brief Check a table of configured options for a particular option
*
* \param[in,out] options Name/value pairs for configured options
* \param[in] validate If not NULL, validator function for option value
* \param[in] name Option name to look for
* \param[in] old_name Alternative option name to look for
* \param[in] def_value Default to use if option not configured
*
* \return Option value (from supplied options table or default value)
*/
static const char *
cluster_option_value(GHashTable *options, bool (*validate)(const char *),
const char *name, const char *old_name,
const char *def_value)
{
const char *value = NULL;
char *new_value = NULL;
CRM_ASSERT(name != NULL);
if (options) {
value = g_hash_table_lookup(options, name);
if ((value == NULL) && old_name) {
value = g_hash_table_lookup(options, old_name);
if (value != NULL) {
pcmk__config_warn("Support for legacy name '%s' for cluster "
"option '%s' is deprecated and will be "
"removed in a future release",
old_name, name);
// Inserting copy with current name ensures we only warn once
new_value = strdup(value);
g_hash_table_insert(options, strdup(name), new_value);
value = new_value;
}
}
if (value && validate && (validate(value) == FALSE)) {
pcmk__config_err("Using default value for cluster option '%s' "
"because '%s' is invalid", name, value);
value = NULL;
}
if (value) {
return value;
}
}
// No value found, use default
value = def_value;
if (value == NULL) {
crm_trace("No value or default provided for cluster option '%s'",
name);
return NULL;
}
if (validate) {
CRM_CHECK(validate(value) != FALSE,
crm_err("Bug: default value for cluster option '%s' is invalid", name);
return NULL);
}
crm_trace("Using default value '%s' for cluster option '%s'",
value, name);
if (options) {
new_value = strdup(value);
g_hash_table_insert(options, strdup(name), new_value);
value = new_value;
}
return value;
}
/*!
* \internal
* \brief Get the value of a cluster option
*
* \param[in,out] options Name/value pairs for configured options
* \param[in] option_list Possible cluster options
* \param[in] len Length of \p option_list
* \param[in] name (Primary) option name to look for
*
* \return Option value
*/
const char *
pcmk__cluster_option(GHashTable *options,
const pcmk__cluster_option_t *option_list,
int len, const char *name)
{
const char *value = NULL;
for (int lpc = 0; lpc < len; lpc++) {
if (pcmk__str_eq(name, option_list[lpc].name, pcmk__str_casei)) {
value = cluster_option_value(options, option_list[lpc].is_valid,
option_list[lpc].name,
option_list[lpc].alt_name,
option_list[lpc].default_value);
return value;
}
}
CRM_CHECK(FALSE, crm_err("Bug: looking for unknown option '%s'", name));
return NULL;
}
/*!
* \internal
* \brief Add a description element to a meta-data string
*
* \param[in,out] s Meta-data string to add to
* \param[in] tag Name of element to add (\c PCMK_XE_LONGDESC or
* \c PCMK_XE_SHORTDESC)
* \param[in] desc Textual description to add
* \param[in] values If not \p NULL, the allowed values for the parameter
* \param[in] spaces If not \p NULL, spaces to insert at the beginning of
* each line
*/
static void
add_desc(GString *s, const char *tag, const char *desc, const char *values,
const char *spaces)
{
char *escaped_en = crm_xml_escape(desc);
if (spaces != NULL) {
g_string_append(s, spaces);
}
- pcmk__g_strcat(s, "<", tag, " " PCMK_XA_LANG "=\"en\">", escaped_en, NULL);
+ pcmk__g_strcat(s,
+ "<", tag, " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">",
+ escaped_en, NULL);
if (values != NULL) {
// Append a period if desc doesn't end in "." or ".)"
if (!pcmk__str_empty(escaped_en)
&& (s->str[s->len - 1] != '.')
&& ((s->str[s->len - 2] != '.') || (s->str[s->len - 1] != ')'))) {
g_string_append_c(s, '.');
}
pcmk__g_strcat(s, " Allowed values: ", values, NULL);
g_string_append_c(s, '.');
}
pcmk__g_strcat(s, "</", tag, ">\n", NULL);
#ifdef ENABLE_NLS
{
static const char *locale = NULL;
char *localized = crm_xml_escape(_(desc));
if (strcmp(escaped_en, localized) != 0) {
if (locale == NULL) {
locale = strtok(setlocale(LC_ALL, NULL), "_");
}
if (spaces != NULL) {
g_string_append(s, spaces);
}
pcmk__g_strcat(s,
"<", tag, " " PCMK_XA_LANG "=\"", locale, "\">",
localized, NULL);
if (values != NULL) {
pcmk__g_strcat(s, _(" Allowed values: "), _(values), NULL);
}
pcmk__g_strcat(s, "</", tag, ">\n", NULL);
}
free(localized);
}
#endif
free(escaped_en);
}
gchar *
pcmk__format_option_metadata(const char *name, const char *desc_short,
const char *desc_long,
pcmk__cluster_option_t *option_list, int len)
{
/* big enough to hold "pacemaker-schedulerd metadata" output */
GString *s = g_string_sized_new(13000);
pcmk__g_strcat(s,
"<?xml " PCMK_XA_VERSION "=\"1.0\"?>\n"
"<" PCMK_XE_RESOURCE_AGENT " "
PCMK_XA_NAME "=\"", name, "\" "
PCMK_XA_VERSION "=\"" PACEMAKER_VERSION "\">\n"
" <" PCMK_XE_VERSION ">" PCMK_OCF_VERSION
"</" PCMK_XE_VERSION ">\n", NULL);
add_desc(s, PCMK_XE_LONGDESC, desc_long, NULL, " ");
add_desc(s, PCMK_XE_SHORTDESC, desc_short, NULL, " ");
g_string_append(s, " <" PCMK_XE_PARAMETERS ">\n");
for (int lpc = 0; lpc < len; lpc++) {
const char *opt_name = option_list[lpc].name;
const char *opt_type = option_list[lpc].type;
const char *opt_values = option_list[lpc].values;
const char *opt_default = option_list[lpc].default_value;
const char *opt_desc_short = option_list[lpc].description_short;
const char *opt_desc_long = option_list[lpc].description_long;
// The standard requires long and short parameter descriptions
CRM_ASSERT((opt_desc_short != NULL) || (opt_desc_long != NULL));
if (opt_desc_short == NULL) {
opt_desc_short = opt_desc_long;
} else if (opt_desc_long == NULL) {
opt_desc_long = opt_desc_short;
}
// The standard requires a parameter type
CRM_ASSERT(opt_type != NULL);
pcmk__g_strcat(s,
" <" PCMK_XE_PARAMETER " "
PCMK_XA_NAME "=\"", opt_name, "\">\n", NULL);
add_desc(s, PCMK_XE_LONGDESC, opt_desc_long, opt_values, " ");
add_desc(s, PCMK_XE_SHORTDESC, opt_desc_short, NULL, " ");
pcmk__g_strcat(s, " <content type=\"", opt_type, "\"", NULL);
if (opt_default != NULL) {
pcmk__g_strcat(s, " default=\"", opt_default, "\"", NULL);
}
if ((opt_values != NULL) && (strcmp(opt_type, "select") == 0)) {
char *str = strdup(opt_values);
const char *delim = ", ";
char *ptr = strtok(str, delim);
g_string_append(s, ">\n");
while (ptr != NULL) {
pcmk__g_strcat(s, " <option value=\"", ptr, "\" />\n",
NULL);
ptr = strtok(NULL, delim);
}
g_string_append_printf(s, " </content>\n");
free(str);
} else {
g_string_append(s, "/>\n");
}
g_string_append(s, " </" PCMK_XE_PARAMETER ">\n");
}
g_string_append(s,
" </" PCMK_XE_PARAMETERS ">\n"
"</" PCMK_XE_RESOURCE_AGENT ">\n");
return g_string_free(s, FALSE);
}
void
pcmk__validate_cluster_options(GHashTable *options,
pcmk__cluster_option_t *option_list, int len)
{
for (int lpc = 0; lpc < len; lpc++) {
cluster_option_value(options, option_list[lpc].is_valid,
option_list[lpc].name,
option_list[lpc].alt_name,
option_list[lpc].default_value);
}
}
diff --git a/lib/common/output_html.c b/lib/common/output_html.c
index ea6f2e6ffe..1baff33481 100644
--- a/lib/common/output_html.c
+++ b/lib/common/output_html.c
@@ -1,478 +1,478 @@
/*
* Copyright 2019-2024 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 <ctype.h>
#include <libxml/HTMLtree.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <crm/msg_xml.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/xml.h>
static const char *stylesheet_default =
".bold { font-weight: bold }\n"
".online { color: green }\n"
".offline { color: red }\n"
".maint { color: blue }\n"
".standby { color: blue }\n"
".health_red { color: red }\n"
".health_yellow { color: GoldenRod }\n"
".rsc-failed { color: red }\n"
".rsc-failure-ignored { color: DarkGreen }\n"
".rsc-managed { color: blue }\n"
".rsc-multiple { color: orange }\n"
".rsc-ok { color: green }\n"
".warning { color: red; font-weight: bold }";
static gboolean cgi_output = FALSE;
static char *stylesheet_link = NULL;
static char *title = NULL;
static GSList *extra_headers = NULL;
GOptionEntry pcmk__html_output_entries[] = {
{ "html-cgi", 0, 0, G_OPTION_ARG_NONE, &cgi_output,
"Add CGI headers (requires --output-as=html)",
NULL },
{ "html-stylesheet", 0, 0, G_OPTION_ARG_STRING, &stylesheet_link,
"Link to an external stylesheet (requires --output-as=html)",
"URI" },
{ "html-title", 0, 0, G_OPTION_ARG_STRING, &title,
"Specify a page title (requires --output-as=html)",
"TITLE" },
{ NULL }
};
/* The first several elements of this struct must be the same as the first
* several elements of private_data_s in lib/common/output_xml.c. This
* struct gets passed to a bunch of the pcmk__output_xml_* functions which
* assume an XML private_data_s. Keeping them laid out the same means this
* still works.
*/
typedef struct private_data_s {
/* Begin members that must match the XML version */
xmlNode *root;
GQueue *parent_q;
GSList *errors;
/* End members that must match the XML version */
} private_data_t;
static void
html_free_priv(pcmk__output_t *out) {
private_data_t *priv = NULL;
if (out == NULL || out->priv == NULL) {
return;
}
priv = out->priv;
xmlFreeNode(priv->root);
g_queue_free(priv->parent_q);
g_slist_free(priv->errors);
free(priv);
out->priv = NULL;
}
static bool
html_init(pcmk__output_t *out) {
private_data_t *priv = NULL;
CRM_ASSERT(out != NULL);
/* If html_init was previously called on this output struct, just return. */
if (out->priv != NULL) {
return true;
} else {
out->priv = calloc(1, sizeof(private_data_t));
if (out->priv == NULL) {
return false;
}
priv = out->priv;
}
priv->parent_q = g_queue_new();
priv->root = create_xml_node(NULL, "html");
xmlCreateIntSubset(priv->root->doc, (pcmkXmlStr) "html", NULL, NULL);
- crm_xml_add(priv->root, PCMK_XA_LANG, "en");
+ crm_xml_add(priv->root, PCMK_XA_LANG, PCMK__VALUE_EN);
g_queue_push_tail(priv->parent_q, priv->root);
priv->errors = NULL;
pcmk__output_xml_create_parent(out, "body", NULL);
return true;
}
static void
add_error_node(gpointer data, gpointer user_data) {
char *str = (char *) data;
pcmk__output_t *out = (pcmk__output_t *) user_data;
out->list_item(out, NULL, "%s", str);
}
static void
html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_dest) {
private_data_t *priv = NULL;
htmlNodePtr head_node = NULL;
htmlNodePtr charset_node = NULL;
CRM_ASSERT(out != NULL);
priv = out->priv;
/* If root is NULL, html_init failed and we are being called from pcmk__output_free
* in the pcmk__output_new path.
*/
if (priv == NULL || priv->root == NULL) {
return;
}
if (cgi_output && print) {
fprintf(out->dest, "Content-Type: text/html\n\n");
}
/* Add the head node last - it's not needed earlier because it doesn't contain
* anything else that the user could add, and we want it done last to pick up
* any options that may have been given.
*/
head_node = xmlNewDocRawNode(NULL, NULL, (pcmkXmlStr) "head", NULL);
if (title != NULL ) {
pcmk_create_xml_text_node(head_node, "title", title);
} else if (out->request != NULL) {
pcmk_create_xml_text_node(head_node, "title", out->request);
}
charset_node = create_xml_node(head_node, "meta");
crm_xml_add(charset_node, "charset", "utf-8");
/* Add any extra header nodes the caller might have created. */
for (int i = 0; i < g_slist_length(extra_headers); i++) {
xmlAddChild(head_node, xmlCopyNode(g_slist_nth_data(extra_headers, i), 1));
}
/* Stylesheets are included two different ways. The first is via a built-in
* default (see the stylesheet_default const above). The second is via the
* html-stylesheet option, and this should obviously be a link to a
* stylesheet. The second can override the first. At least one should be
* given.
*/
pcmk_create_xml_text_node(head_node, "style", stylesheet_default);
if (stylesheet_link != NULL) {
htmlNodePtr link_node = create_xml_node(head_node, "link");
pcmk__xe_set_props(link_node, "rel", "stylesheet",
"href", stylesheet_link,
NULL);
}
xmlAddPrevSibling(priv->root->children, head_node);
if (g_slist_length(priv->errors) > 0) {
out->begin_list(out, "Errors", NULL, NULL);
g_slist_foreach(priv->errors, add_error_node, (gpointer) out);
out->end_list(out);
}
if (print) {
htmlDocDump(out->dest, priv->root->doc);
}
if (copy_dest != NULL) {
*copy_dest = copy_xml(priv->root);
}
g_slist_free_full(extra_headers, (GDestroyNotify) xmlFreeNode);
extra_headers = NULL;
}
static void
html_reset(pcmk__output_t *out) {
CRM_ASSERT(out != NULL);
out->dest = freopen(NULL, "w", out->dest);
CRM_ASSERT(out->dest != NULL);
html_free_priv(out);
html_init(out);
}
static void
html_subprocess_output(pcmk__output_t *out, int exit_status,
const char *proc_stdout, const char *proc_stderr) {
char *rc_buf = NULL;
CRM_ASSERT(out != NULL);
rc_buf = crm_strdup_printf("Return code: %d", exit_status);
pcmk__output_create_xml_text_node(out, "h2", "Command Output");
pcmk__output_create_html_node(out, "div", NULL, NULL, rc_buf);
if (proc_stdout != NULL) {
pcmk__output_create_html_node(out, "div", NULL, NULL, "Stdout");
pcmk__output_create_html_node(out, "div", NULL, "output", proc_stdout);
}
if (proc_stderr != NULL) {
pcmk__output_create_html_node(out, "div", NULL, NULL, "Stderr");
pcmk__output_create_html_node(out, "div", NULL, "output", proc_stderr);
}
free(rc_buf);
}
static void
html_version(pcmk__output_t *out, bool extended) {
CRM_ASSERT(out != NULL);
pcmk__output_create_xml_text_node(out, "h2", "Version Information");
pcmk__output_create_html_node(out, "div", NULL, NULL, "Program: Pacemaker");
pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Version: %s", PACEMAKER_VERSION));
pcmk__output_create_html_node(out, "div", NULL, NULL,
"Author: Andrew Beekhof and "
"the Pacemaker project contributors");
pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Build: %s", BUILD_VERSION));
pcmk__output_create_html_node(out, "div", NULL, NULL, crm_strdup_printf("Features: %s", CRM_FEATURES));
}
G_GNUC_PRINTF(2, 3)
static void
html_err(pcmk__output_t *out, const char *format, ...) {
private_data_t *priv = NULL;
int len = 0;
char *buf = NULL;
va_list ap;
CRM_ASSERT(out != NULL && out->priv != NULL);
priv = out->priv;
va_start(ap, format);
len = vasprintf(&buf, format, ap);
CRM_ASSERT(len >= 0);
va_end(ap);
priv->errors = g_slist_append(priv->errors, buf);
}
G_GNUC_PRINTF(2, 3)
static int
html_info(pcmk__output_t *out, const char *format, ...) {
return pcmk_rc_no_output;
}
static void
html_output_xml(pcmk__output_t *out, const char *name, const char *buf) {
htmlNodePtr node = NULL;
CRM_ASSERT(out != NULL);
node = pcmk__output_create_html_node(out, "pre", NULL, NULL, buf);
crm_xml_add(node, PCMK_XA_LANG, "xml");
}
G_GNUC_PRINTF(4, 5)
static void
html_begin_list(pcmk__output_t *out, const char *singular_noun,
const char *plural_noun, const char *format, ...) {
int q_len = 0;
private_data_t *priv = NULL;
xmlNodePtr node = NULL;
CRM_ASSERT(out != NULL && out->priv != NULL);
priv = out->priv;
/* If we are already in a list (the queue depth is always at least
* one because of the <html> element), first create a <li> element
* to hold the <h2> and the new list.
*/
q_len = g_queue_get_length(priv->parent_q);
if (q_len > 2) {
pcmk__output_xml_create_parent(out, "li", NULL);
}
if (format != NULL) {
va_list ap;
char *buf = NULL;
int len;
va_start(ap, format);
len = vasprintf(&buf, format, ap);
va_end(ap);
CRM_ASSERT(len >= 0);
if (q_len > 2) {
pcmk__output_create_xml_text_node(out, "h3", buf);
} else {
pcmk__output_create_xml_text_node(out, "h2", buf);
}
free(buf);
}
node = pcmk__output_xml_create_parent(out, "ul", NULL);
g_queue_push_tail(priv->parent_q, node);
}
G_GNUC_PRINTF(3, 4)
static void
html_list_item(pcmk__output_t *out, const char *name, const char *format, ...) {
htmlNodePtr item_node = NULL;
va_list ap;
char *buf = NULL;
int len;
CRM_ASSERT(out != NULL);
va_start(ap, format);
len = vasprintf(&buf, format, ap);
CRM_ASSERT(len >= 0);
va_end(ap);
item_node = pcmk__output_create_xml_text_node(out, "li", buf);
free(buf);
if (name != NULL) {
crm_xml_add(item_node, PCMK_XA_CLASS, name);
}
}
static void
html_increment_list(pcmk__output_t *out) {
/* This function intentially left blank */
}
static void
html_end_list(pcmk__output_t *out) {
private_data_t *priv = NULL;
CRM_ASSERT(out != NULL && out->priv != NULL);
priv = out->priv;
/* Remove the <ul> tag. */
g_queue_pop_tail(priv->parent_q);
pcmk__output_xml_pop_parent(out);
/* Remove the <li> created for nested lists. */
if (g_queue_get_length(priv->parent_q) > 2) {
pcmk__output_xml_pop_parent(out);
}
}
static bool
html_is_quiet(pcmk__output_t *out) {
return false;
}
static void
html_spacer(pcmk__output_t *out) {
CRM_ASSERT(out != NULL);
pcmk__output_create_xml_node(out, "br", NULL);
}
static void
html_progress(pcmk__output_t *out, bool end) {
/* This function intentially left blank */
}
pcmk__output_t *
pcmk__mk_html_output(char **argv) {
pcmk__output_t *retval = calloc(1, sizeof(pcmk__output_t));
if (retval == NULL) {
return NULL;
}
retval->fmt_name = "html";
retval->request = pcmk__quote_cmdline(argv);
retval->init = html_init;
retval->free_priv = html_free_priv;
retval->finish = html_finish;
retval->reset = html_reset;
retval->register_message = pcmk__register_message;
retval->message = pcmk__call_message;
retval->subprocess_output = html_subprocess_output;
retval->version = html_version;
retval->info = html_info;
retval->transient = html_info;
retval->err = html_err;
retval->output_xml = html_output_xml;
retval->begin_list = html_begin_list;
retval->list_item = html_list_item;
retval->increment_list = html_increment_list;
retval->end_list = html_end_list;
retval->is_quiet = html_is_quiet;
retval->spacer = html_spacer;
retval->progress = html_progress;
retval->prompt = pcmk__text_prompt;
return retval;
}
xmlNodePtr
pcmk__output_create_html_node(pcmk__output_t *out, const char *element_name, const char *id,
const char *class_name, const char *text) {
htmlNodePtr node = NULL;
CRM_ASSERT(out != NULL);
CRM_CHECK(pcmk__str_eq(out->fmt_name, "html", pcmk__str_none), return NULL);
node = pcmk__output_create_xml_text_node(out, element_name, text);
if (class_name != NULL) {
crm_xml_add(node, PCMK_XA_CLASS, class_name);
}
if (id != NULL) {
crm_xml_add(node, PCMK_XA_ID, id);
}
return node;
}
void
pcmk__html_add_header(const char *name, ...) {
htmlNodePtr header_node;
va_list ap;
va_start(ap, name);
header_node = xmlNewDocRawNode(NULL, NULL, (pcmkXmlStr) name, NULL);
while (1) {
char *key = va_arg(ap, char *);
char *value;
if (key == NULL) {
break;
}
value = va_arg(ap, char *);
crm_xml_add(header_node, key, value);
}
extra_headers = g_slist_append(extra_headers, header_node);
va_end(ap);
}
diff --git a/lib/fencing/st_lha.c b/lib/fencing/st_lha.c
index 5bd8f3ed13..8224914cd5 100644
--- a/lib/fencing/st_lha.c
+++ b/lib/fencing/st_lha.c
@@ -1,286 +1,286 @@
/*
* Copyright 2004-2024 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 <stdarg.h>
#include <string.h>
#include <errno.h>
#include <glib.h>
#include <dlfcn.h>
#include <crm/crm.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <stonith/stonith.h>
#include "fencing_private.h"
#define LHA_STONITH_LIBRARY "libstonith.so.1"
static void *lha_agents_lib = NULL;
// @TODO Use XML string constants and maybe a real XML object
static const char META_TEMPLATE[] =
"<?xml " PCMK_XA_VERSION "=\"1.0\"?>\n"
"<" PCMK_XE_RESOURCE_AGENT " " PCMK_XA_NAME "=\"%s\">\n"
" <" PCMK_XE_VERSION ">1.0</" PCMK_XE_VERSION ">\n"
- " <" PCMK_XE_LONGDESC " " PCMK_XA_LANG "=\"en\">\n"
+ " <" PCMK_XE_LONGDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">\n"
"%s\n"
" </" PCMK_XE_LONGDESC ">\n"
- " <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "=\"en\">"
+ " <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">"
"%s"
"</" PCMK_XE_SHORTDESC ">\n"
"%s\n"
" <actions>\n"
" <action name=\"start\" timeout=\"%s\" />\n"
" <action name=\"stop\" timeout=\"15\" />\n"
" <action name=\"status\" timeout=\"%s\" />\n"
" <action name=\"monitor\" timeout=\"%s\" interval=\"3600\"/>\n"
" <action name=\"meta-data\" timeout=\"15\" />\n"
" </actions>\n"
" <special tag=\"heartbeat\">\n"
" <" PCMK_XE_VERSION ">2.0</" PCMK_XE_VERSION ">\n"
" </special>\n"
"</" PCMK_XE_RESOURCE_AGENT ">\n";
static void *
find_library_function(void **handle, const char *lib, const char *fn)
{
void *a_function;
if (*handle == NULL) {
*handle = dlopen(lib, RTLD_LAZY);
if ((*handle) == NULL) {
crm_err("Could not open %s: %s", lib, dlerror());
return NULL;
}
}
a_function = dlsym(*handle, fn);
if (a_function == NULL) {
crm_err("Could not find %s in %s: %s", fn, lib, dlerror());
}
return a_function;
}
/*!
* \internal
* \brief Check whether a given fence agent is an LHA agent
*
* \param[in] agent Fence agent type
*
* \return true if \p agent is an LHA agent, otherwise false
*/
bool
stonith__agent_is_lha(const char *agent)
{
Stonith *stonith_obj = NULL;
static bool need_init = true;
static Stonith *(*st_new_fn) (const char *) = NULL;
static void (*st_del_fn) (Stonith *) = NULL;
if (need_init) {
need_init = false;
st_new_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY,
"stonith_new");
st_del_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY,
"stonith_delete");
}
if (lha_agents_lib && st_new_fn && st_del_fn) {
stonith_obj = (*st_new_fn) (agent);
if (stonith_obj) {
(*st_del_fn) (stonith_obj);
return true;
}
}
return false;
}
int
stonith__list_lha_agents(stonith_key_value_t **devices)
{
static gboolean need_init = TRUE;
int count = 0;
char **entry = NULL;
char **type_list = NULL;
static char **(*type_list_fn) (void) = NULL;
static void (*type_free_fn) (char **) = NULL;
if (need_init) {
need_init = FALSE;
type_list_fn = find_library_function(&lha_agents_lib,
LHA_STONITH_LIBRARY,
"stonith_types");
type_free_fn = find_library_function(&lha_agents_lib,
LHA_STONITH_LIBRARY,
"stonith_free_hostlist");
}
if (type_list_fn) {
type_list = (*type_list_fn) ();
}
for (entry = type_list; entry != NULL && *entry; ++entry) {
crm_trace("Added: %s", *entry);
*devices = stonith_key_value_add(*devices, NULL, *entry);
count++;
}
if (type_list && type_free_fn) {
(*type_free_fn) (type_list);
}
return count;
}
static void
stonith_plugin(int priority, const char *fmt, ...) G_GNUC_PRINTF(2, 3);
static void
stonith_plugin(int priority, const char *format, ...)
{
int err = errno;
va_list ap;
int len = 0;
char *string = NULL;
va_start(ap, format);
len = vasprintf (&string, format, ap);
va_end(ap);
CRM_ASSERT(len > 0);
do_crm_log_alias(priority, __FILE__, __func__, __LINE__, "%s", string);
free(string);
errno = err;
}
int
stonith__lha_metadata(const char *agent, int timeout, char **output)
{
int rc = 0;
char *buffer = NULL;
static const char *no_parameter_info = "<!-- no value -->";
Stonith *stonith_obj = NULL;
static gboolean need_init = TRUE;
static Stonith *(*st_new_fn) (const char *) = NULL;
static const char *(*st_info_fn) (Stonith *, int) = NULL;
static void (*st_del_fn) (Stonith *) = NULL;
static void (*st_log_fn) (Stonith *, PILLogFun) = NULL;
if (need_init) {
need_init = FALSE;
st_new_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY,
"stonith_new");
st_del_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY,
"stonith_delete");
st_log_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY,
"stonith_set_log");
st_info_fn = find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY,
"stonith_get_info");
}
if (lha_agents_lib && st_new_fn && st_del_fn && st_info_fn && st_log_fn) {
char *xml_meta_longdesc = NULL;
char *xml_meta_shortdesc = NULL;
char *meta_param = NULL;
char *meta_longdesc = NULL;
char *meta_shortdesc = NULL;
const char *timeout_str = NULL;
stonith_obj = (*st_new_fn) (agent);
if (stonith_obj) {
(*st_log_fn) (stonith_obj, (PILLogFun) & stonith_plugin);
pcmk__str_update(&meta_longdesc,
(*st_info_fn) (stonith_obj, ST_DEVICEDESCR));
if (meta_longdesc == NULL) {
crm_warn("no long description in %s's metadata.", agent);
meta_longdesc = strdup(no_parameter_info);
}
pcmk__str_update(&meta_shortdesc,
(*st_info_fn) (stonith_obj, ST_DEVICEID));
if (meta_shortdesc == NULL) {
crm_warn("no short description in %s's metadata.", agent);
meta_shortdesc = strdup(no_parameter_info);
}
pcmk__str_update(&meta_param,
(*st_info_fn) (stonith_obj, ST_CONF_XML));
if (meta_param == NULL) {
crm_warn("no list of parameters in %s's metadata.", agent);
meta_param = strdup(no_parameter_info);
}
(*st_del_fn) (stonith_obj);
} else {
errno = EINVAL;
crm_perror(LOG_ERR, "Agent %s not found", agent);
return -EINVAL;
}
xml_meta_longdesc =
(char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_longdesc);
xml_meta_shortdesc =
(char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_shortdesc);
timeout_str = pcmk__readable_interval(PCMK_DEFAULT_ACTION_TIMEOUT_MS);
buffer = crm_strdup_printf(META_TEMPLATE, agent, xml_meta_longdesc,
xml_meta_shortdesc, meta_param,
timeout_str, timeout_str, timeout_str);
xmlFree(xml_meta_longdesc);
xmlFree(xml_meta_shortdesc);
free(meta_shortdesc);
free(meta_longdesc);
free(meta_param);
}
if (output) {
*output = buffer;
} else {
free(buffer);
}
return rc;
}
/* Implement a dummy function that uses -lpils so that linkers don't drop the
* reference.
*/
#include <pils/plugin.h>
const char *i_hate_pils(int rc);
const char *
i_hate_pils(int rc)
{
return PIL_strerror(rc);
}
int
stonith__lha_validate(stonith_t *st, int call_options, const char *target,
const char *agent, GHashTable *params, int timeout,
char **output, char **error_output)
{
errno = EOPNOTSUPP;
crm_perror(LOG_ERR, "Cannot validate Linux-HA fence agents");
return -EOPNOTSUPP;
}
diff --git a/lib/services/services_lsb.c b/lib/services/services_lsb.c
index e85676603c..23e928fdda 100644
--- a/lib/services/services_lsb.c
+++ b/lib/services/services_lsb.c
@@ -1,346 +1,346 @@
/*
* Copyright 2010-2024 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/msg_xml.h>
#include <crm/services.h>
#include "services_private.h"
#include "services_lsb.h"
// @TODO Use XML string constants and maybe a real XML object
#define lsb_metadata_template \
"<?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.0</" PCMK_XE_VERSION ">\n" \
- " <" PCMK_XE_LONGDESC " " PCMK_XA_LANG "='en'>\n" \
+ " <" PCMK_XE_LONGDESC " " PCMK_XA_LANG "='" PCMK__VALUE_EN "'>\n" \
"%s" \
" </" PCMK_XE_LONGDESC ">\n" \
- " <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "='en'>" \
+ " <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "='" PCMK__VALUE_EN "'>" \
"%s" \
"</" PCMK_XE_SHORTDESC">\n" \
" <" PCMK_XE_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" \
"</" PCMK_XE_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 REQUIRED_START "# Required-Start:"
#define REQUIRED_STOP "# Required-Stop:"
#define SHOULD_START "# Should-Start:"
#define SHOULD_STOP "# Should-Stop:"
#define DEFAULT_START "# Default-Start:"
#define DEFAULT_STOP "# Default-Stop:"
#define SHORT_DESC "# 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 *required_start = NULL;
char *required_stop = NULL;
char *should_start = NULL;
char *should_stop = NULL;
char *default_start = NULL;
char *default_stop = NULL;
char *short_desc = NULL;
char *long_desc = 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, &required_start,
REQUIRED_START)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &required_stop, REQUIRED_STOP)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &should_start, SHOULD_START)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &should_stop, SHOULD_STOP)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &default_start, DEFAULT_START)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &default_stop, DEFAULT_STOP)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &short_desc, SHORT_DESC)) {
continue;
}
/* Long description may cross multiple lines */
if ((long_desc == 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
long_desc =
(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,
pcmk__s(long_desc, type),
pcmk__s(short_desc, type),
pcmk__s(provides, ""),
pcmk__s(required_start, ""),
pcmk__s(required_stop, ""),
pcmk__s(should_start, ""),
pcmk__s(should_stop, ""),
pcmk__s(default_start, ""),
pcmk__s(default_stop, ""));
lsb_meta_helper_free_value(long_desc);
lsb_meta_helper_free_value(short_desc);
lsb_meta_helper_free_value(provides);
lsb_meta_helper_free_value(required_start);
lsb_meta_helper_free_value(required_stop);
lsb_meta_helper_free_value(should_start);
lsb_meta_helper_free_value(should_stop);
lsb_meta_helper_free_value(default_start);
lsb_meta_helper_free_value(default_stop);
crm_trace("Created fake metadata: %zu", 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, PCMK_ACTION_STATUS, PCMK_ACTION_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/systemd.c b/lib/services/systemd.c
index 8efe80506e..8781f4dca3 100644
--- a/lib/services/systemd.c
+++ b/lib/services/systemd.c
@@ -1,1115 +1,1115 @@
/*
* Copyright 2012-2024 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/msg_xml.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, 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);
}
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,
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() == 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;
}
// @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 "=\"en\">\n" \
+ " <" PCMK_XE_LONGDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">\n" \
" %s\n" \
" </" PCMK_XE_LONGDESC ">\n" \
- " <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "=\"en\">" \
+ " <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">" \
"systemd unit file for %s" \
"</" PCMK_XE_SHORTDESC ">\n" \
" <" PCMK_XE_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" \
"</" PCMK_XE_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;
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__)) {
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, 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)) {
method = "StartUnit";
systemd_create_override(op->agent, op->timeout);
} 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);
CRM_ASSERT(msg != NULL);
/* (ss) */
{
const char *replace_s = "replace";
char *name = systemd_service_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);
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, 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) {
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/upstart.c b/lib/services/upstart.c
index 4fc90dfe69..06f4263fe7 100644
--- a/lib/services/upstart.c
+++ b/lib/services/upstart.c
@@ -1,707 +1,707 @@
/*
* Original copyright 2010 Senko Rasic <senko.rasic@dobarkod.hr>
* and Ante Karamatic <ivoks@init.hr>
* Later changes copyright 2012-2024 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/msg_xml.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);
}
}
// @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 "=\"en\">\n" \
+ " <" PCMK_XE_LONGDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">\n" \
" Upstart agent for controlling the system %s service\n" \
" </" PCMK_XE_LONGDESC ">\n" \
- " <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "=\"en\">" \
+ " <" PCMK_XE_SHORTDESC " " PCMK_XA_LANG "=\"" PCMK__VALUE_EN "\">" \
"Upstart job for %s" \
"</" PCMK_XE_SHORTDESC ">\n" \
" <" PCMK_XE_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" \
"</" PCMK_XE_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, PCMK_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, PCMK_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, PCMK_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, PCMK_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, PCMK_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, PCMK_ACTION_MONITOR,
PCMK_ACTION_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, PCMK_ACTION_START, pcmk__str_none)) {
action = "Start";
} else if (pcmk__str_eq(action, PCMK_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, PCMK_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);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Sat, Jan 25, 11:59 AM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1322479
Default Alt Text
(121 KB)

Event Timeline