diff --git a/include/crm/common/options.h b/include/crm/common/options.h
index d06e98b59c..69dc5ccd07 100644
--- a/include/crm/common/options.h
+++ b/include/crm/common/options.h
@@ -1,209 +1,210 @@
 /*
  * Copyright 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__CRM_COMMON_OPTIONS__H
 #  define PCMK__CRM_COMMON_OPTIONS__H
 
 #ifdef __cplusplus
 extern     "C" {
 #endif
 
 /**
  * \file
  * \brief API related to options
  * \ingroup core
  */
 
 /*
  * Cluster options
  */
 
 #define PCMK_OPT_BATCH_LIMIT                    "batch-limit"
 #define PCMK_OPT_CLUSTER_DELAY                  "cluster-delay"
 #define PCMK_OPT_CLUSTER_INFRASTRUCTURE         "cluster-infrastructure"
 #define PCMK_OPT_CLUSTER_IPC_LIMIT              "cluster-ipc-limit"
 #define PCMK_OPT_CLUSTER_NAME                   "cluster-name"
 #define PCMK_OPT_CLUSTER_RECHECK_INTERVAL       "cluster-recheck-interval"
 #define PCMK_OPT_CONCURRENT_FENCING             "concurrent-fencing"
 #define PCMK_OPT_DC_DEADTIME                    "dc-deadtime"
 #define PCMK_OPT_DC_VERSION                     "dc-version"
 #define PCMK_OPT_ELECTION_TIMEOUT               "election-timeout"
 #define PCMK_OPT_ENABLE_ACL                     "enable-acl"
 #define PCMK_OPT_ENABLE_STARTUP_PROBES          "enable-startup-probes"
 #define PCMK_OPT_FENCE_REACTION                 "fence-reaction"
 #define PCMK_OPT_HAVE_WATCHDOG                  "have-watchdog"
 #define PCMK_OPT_JOIN_FINALIZATION_TIMEOUT      "join-finalization-timeout"
 #define PCMK_OPT_JOIN_INTEGRATION_TIMEOUT       "join-integration-timeout"
 #define PCMK_OPT_LOAD_THRESHOLD                 "load-threshold"
 #define PCMK_OPT_MAINTENANCE_MODE               "maintenance-mode"
 #define PCMK_OPT_MIGRATION_LIMIT                "migration-limit"
 #define PCMK_OPT_NO_QUORUM_POLICY               "no-quorum-policy"
 #define PCMK_OPT_NODE_ACTION_LIMIT              "node-action-limit"
 #define PCMK_OPT_NODE_HEALTH_BASE               "node-health-base"
 #define PCMK_OPT_NODE_HEALTH_GREEN              "node-health-green"
 #define PCMK_OPT_NODE_HEALTH_RED                "node-health-red"
 #define PCMK_OPT_NODE_HEALTH_STRATEGY           "node-health-strategy"
 #define PCMK_OPT_NODE_HEALTH_YELLOW             "node-health-yellow"
 #define PCMK_OPT_NODE_PENDING_TIMEOUT           "node-pending-timeout"
 #define PCMK_OPT_PE_ERROR_SERIES_MAX            "pe-error-series-max"
 #define PCMK_OPT_PE_INPUT_SERIES_MAX            "pe-input-series-max"
 #define PCMK_OPT_PE_WARN_SERIES_MAX             "pe-warn-series-max"
 #define PCMK_OPT_PLACEMENT_STRATEGY             "placement-strategy"
 #define PCMK_OPT_PRIORITY_FENCING_DELAY         "priority-fencing-delay"
 #define PCMK_OPT_SHUTDOWN_ESCALATION            "shutdown-escalation"
 #define PCMK_OPT_SHUTDOWN_LOCK                  "shutdown-lock"
 #define PCMK_OPT_SHUTDOWN_LOCK_LIMIT            "shutdown-lock-limit"
 #define PCMK_OPT_START_FAILURE_IS_FATAL         "start-failure-is-fatal"
 #define PCMK_OPT_STARTUP_FENCING                "startup-fencing"
 #define PCMK_OPT_STONITH_ACTION                 "stonith-action"
 #define PCMK_OPT_STONITH_ENABLED                "stonith-enabled"
 #define PCMK_OPT_STONITH_MAX_ATTEMPTS           "stonith-max-attempts"
 #define PCMK_OPT_STONITH_TIMEOUT                "stonith-timeout"
 #define PCMK_OPT_STONITH_WATCHDOG_TIMEOUT       "stonith-watchdog-timeout"
 #define PCMK_OPT_STOP_ALL_RESOURCES             "stop-all-resources"
 #define PCMK_OPT_STOP_ORPHAN_ACTIONS            "stop-orphan-actions"
 #define PCMK_OPT_STOP_ORPHAN_RESOURCES          "stop-orphan-resources"
 #define PCMK_OPT_SYMMETRIC_CLUSTER              "symmetric-cluster"
 #define PCMK_OPT_TRANSITION_DELAY               "transition-delay"
 
 
 /*
  * Meta-attributes
  */
 
 #define PCMK_META_ALLOW_MIGRATE                 "allow-migrate"
 #define PCMK_META_ALLOW_UNHEALTHY_NODES         "allow-unhealthy-nodes"
 #define PCMK_META_CLONE_MAX                     "clone-max"
 #define PCMK_META_CLONE_MIN                     "clone-min"
 #define PCMK_META_CLONE_NODE_MAX                "clone-node-max"
 #define PCMK_META_CONTAINER_ATTRIBUTE_TARGET    "container-attribute-target"
 #define PCMK_META_CRITICAL                      "critical"
 #define PCMK_META_ENABLED                       "enabled"
 #define PCMK_META_FAILURE_TIMEOUT               "failure-timeout"
 #define PCMK_META_GLOBALLY_UNIQUE               "globally-unique"
 #define PCMK_META_INTERLEAVE                    "interleave"
 #define PCMK_META_INTERVAL                      "interval"
 #define PCMK_META_IS_MANAGED                    "is-managed"
 #define PCMK_META_INTERVAL_ORIGIN               "interval-origin"
 #define PCMK_META_MAINTENANCE                   "maintenance"
 #define PCMK_META_MIGRATION_THRESHOLD           "migration-threshold"
 #define PCMK_META_MULTIPLE_ACTIVE               "multiple-active"
 #define PCMK_META_NOTIFY                        "notify"
 #define PCMK_META_ON_FAIL                       "on-fail"
 #define PCMK_META_ORDERED                       "ordered"
 #define PCMK_META_PRIORITY                      "priority"
 #define PCMK_META_PROMOTABLE                    "promotable"
 #define PCMK_META_PROMOTED_MAX                  "promoted-max"
 #define PCMK_META_PROMOTED_NODE_MAX             "promoted-node-max"
 #define PCMK_META_RECORD_PENDING                "record-pending"
 #define PCMK_META_REMOTE_ADDR                   "remote-addr"
 #define PCMK_META_REMOTE_ALLOW_MIGRATE          "remote-allow-migrate"
 #define PCMK_META_REMOTE_CONNECT_TIMEOUT        "remote-connect-timeout"
 #define PCMK_META_REMOTE_NODE                   "remote-node"
 #define PCMK_META_REMOTE_PORT                   "remote-port"
 #define PCMK_META_REQUIRES                      "requires"
 #define PCMK_META_RESOURCE_STICKINESS           "resource-stickiness"
 #define PCMK_META_START_DELAY                   "start-delay"
 #define PCMK_META_TARGET_ROLE                   "target-role"
 #define PCMK_META_TIMEOUT                       "timeout"
 #define PCMK_META_TIMESTAMP_FORMAT              "timestamp-format"
 
 
 /*
  * Remote resource instance attributes
  */
 
 #define PCMK_REMOTE_RA_ADDR                     "addr"
 #define PCMK_REMOTE_RA_PORT                     "port"
 #define PCMK_REMOTE_RA_RECONNECT_INTERVAL       "reconnect_interval"
 #define PCMK_REMOTE_RA_SERVER                   "server"
 
 
 /*
  * Enumerated values
  */
 
 #define PCMK_VALUE_ALWAYS                       "always"
 #define PCMK_VALUE_AND                          "and"
 #define PCMK_VALUE_BALANCED                     "balanced"
 #define PCMK_VALUE_BLOCK                        "block"
 #define PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS        "cib-bootstrap-options"
 #define PCMK_VALUE_CREATE                       "create"
 #define PCMK_VALUE_DATE_SPEC                    "date_spec"
 #define PCMK_VALUE_DEFAULT                      "default"
 #define PCMK_VALUE_DEFINED                      "defined"
 #define PCMK_VALUE_DELETE                       "delete"
 #define PCMK_VALUE_DEMOTE                       "demote"
 #define PCMK_VALUE_DENY                         "deny"
 #define PCMK_VALUE_EQ                           "eq"
 #define PCMK_VALUE_EXCLUSIVE                    "exclusive"
 #define PCMK_VALUE_FAILED                       "failed"
 #define PCMK_VALUE_FALSE                        "false"
 #define PCMK_VALUE_FENCE                        "fence"
 #define PCMK_VALUE_FENCING                      "fencing"
 #define PCMK_VALUE_FREEZE                       "freeze"
 #define PCMK_VALUE_GRANTED                      "granted"
 #define PCMK_VALUE_GREEN                        "green"
 #define PCMK_VALUE_GT                           "gt"
 #define PCMK_VALUE_GTE                          "gte"
 #define PCMK_VALUE_HOST                         "host"
 #define PCMK_VALUE_IGNORE                       "ignore"
 #define PCMK_VALUE_INTEGER                      "integer"
 #define PCMK_VALUE_LITERAL                      "literal"
 #define PCMK_VALUE_LT                           "lt"
 #define PCMK_VALUE_LTE                          "lte"
 #define PCMK_VALUE_MANDATORY                    "Mandatory"
 #define PCMK_VALUE_MEMBER                       "member"
 #define PCMK_VALUE_META                         "meta"
 #define PCMK_VALUE_MIGRATE_ON_RED               "migrate-on-red"
 #define PCMK_VALUE_MINIMAL                      "minimal"
 #define PCMK_VALUE_MODIFY                       "modify"
 #define PCMK_VALUE_MOVE                         "move"
 #define PCMK_VALUE_NE                           "ne"
 #define PCMK_VALUE_NEVER                        "never"
 #define PCMK_VALUE_NOT_DEFINED                  "not_defined"
 #define PCMK_VALUE_NOTHING                      "nothing"
 #define PCMK_VALUE_NUMBER                       "number"
 #define PCMK_VALUE_OFFLINE                      "offline"
 #define PCMK_VALUE_ONLINE                       "online"
 #define PCMK_VALUE_ONLY_GREEN                   "only-green"
 #define PCMK_VALUE_OPTIONAL                     "Optional"
 #define PCMK_VALUE_OR                           "or"
 #define PCMK_VALUE_PANIC                        "panic"
 #define PCMK_VALUE_PARAM                        "param"
 #define PCMK_VALUE_PENDING                      "pending"
+#define PCMK_VALUE_PROGRESSIVE                  "progressive"
 #define PCMK_VALUE_QUORUM                       "quorum"
 #define PCMK_VALUE_READ                         "read"
 #define PCMK_VALUE_RED                          "red"
 #define PCMK_VALUE_REMOTE                       "remote"
 #define PCMK_VALUE_RESTART                      "restart"
 #define PCMK_VALUE_RESTART_CONTAINER            "restart-container"
 #define PCMK_VALUE_REVOKED                      "revoked"
 #define PCMK_VALUE_SERIALIZE                    "Serialize"
 #define PCMK_VALUE_STANDBY                      "standby"
 #define PCMK_VALUE_STRING                       "string"
 #define PCMK_VALUE_STOP                         "stop"
 #define PCMK_VALUE_SUCCESS                      "success"
 #define PCMK_VALUE_TRUE                         "true"
 #define PCMK_VALUE_UNFENCING                    "unfencing"
 #define PCMK_VALUE_UNKNOWN                      "unknown"
 #define PCMK_VALUE_UTILIZATION                  "utilization"
 #define PCMK_VALUE_VERSION                      "version"
 #define PCMK_VALUE_WRITE                        "write"
 #define PCMK_VALUE_YELLOW                       "yellow"
 
 // @COMPAT This will become a deprecated alias for PCMK_VALUE_FENCE (see T279)
 #define PCMK_VALUE_FENCE_LEGACY                 "suicide"
 
 
 #ifdef __cplusplus
 }
 #endif
 
 #endif // PCMK__CRM_COMMON_OPTIONS__H
diff --git a/include/crm/common/options_internal.h b/include/crm/common/options_internal.h
index 95b604d4eb..bdb193bc6b 100644
--- a/include/crm/common/options_internal.h
+++ b/include/crm/common/options_internal.h
@@ -1,213 +1,212 @@
 /*
  * 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
  */
 
 /*!
  * \internal
  * \enum pcmk__opt_context
  * \brief Context flags for options
  */
 enum pcmk__opt_context {
     // @COMPAT Used only for daemon metadata
     pcmk__opt_context_none       = 0,           //!< No additional context
     pcmk__opt_context_based      = (1 << 1),    //!< CIB manager metadata
     pcmk__opt_context_controld   = (1 << 2),    //!< Controller metadata
     pcmk__opt_context_schedulerd = (1 << 3),    //!< Scheduler metadata
 };
 
 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 *);
 
     // @COMPAT context is used only for daemon meta-data
     enum pcmk__opt_context context;
 
     const char *description_short;
     const char *description_long;
 
 } pcmk__cluster_option_t;
 
 const char *pcmk__cluster_option(GHashTable *options, const char *name);
 
 gchar *pcmk__format_option_metadata(const char *name, const char *desc_short,
                                     const char *desc_long,
                                     enum pcmk__opt_context filter,
                                     pcmk__cluster_option_t *option_list,
                                     int len);
 
 gchar *pcmk__cluster_option_metadata(const char *name, const char *desc_short,
                                      const char *desc_long,
                                      enum pcmk__opt_context filter);
 
 void pcmk__validate_cluster_options(GHashTable *options);
 
 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);
 bool pcmk__valid_placement_strategy(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_DIRECTORY   "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                    "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_ATTRD                   "attrd"
 #define PCMK__VALUE_CIB                     "cib"
 #define PCMK__VALUE_CLUSTER                 "cluster"
 #define PCMK__VALUE_CRMD                    "crmd"
 #define PCMK__VALUE_CUSTOM                  "custom"
 #define PCMK__VALUE_EN                      "en"
 #define PCMK__VALUE_EPOCH                   "epoch"
 #define PCMK__VALUE_INIT                    "init"
 #define PCMK__VALUE_LOCAL                   "local"
 #define PCMK__VALUE_NONE                    "none"
 #define PCMK__VALUE_OUTPUT                  "output"
 #define PCMK__VALUE_PASSWORD                "password"
 #define PCMK__VALUE_PING                    "ping"
-#define PCMK__VALUE_PROGRESSIVE             "progressive"
 #define PCMK__VALUE_REFRESH                 "refresh"
 #define PCMK__VALUE_REQUEST                 "request"
 #define PCMK__VALUE_RESPONSE                "response"
 #define PCMK__VALUE_RUNNING                 "running"
 #define PCMK__VALUE_SHUTDOWN_COMPLETE       "shutdown_complete"
 #define PCMK__VALUE_SHUTTING_DOWN           "shutting_down"
 #define PCMK__VALUE_STARTING_DAEMONS        "starting_daemons"
 #define PCMK__VALUE_WAIT_FOR_PING           "wait_for_ping"
 
 /* @COMPAT Deprecated since 2.1.7 (used with PCMK__XA_ORDERING attribute of
  * resource sets)
  */
 #define PCMK__VALUE_GROUP                   "group"
 
 #endif // PCMK__OPTIONS_INTERNAL__H
diff --git a/lib/common/health.c b/lib/common/health.c
index d85ca2ac15..de0c1e6a14 100644
--- a/lib/common/health.c
+++ b/lib/common/health.c
@@ -1,68 +1,67 @@
 /*
  * Copyright 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>
 
 /*!
  * \internal
  * \brief Ensure a health strategy value is allowed
  *
  * \param[in] value  Configured health strategy
  *
  * \return true if \p value is an allowed health strategy value, otherwise false
  */
 bool
 pcmk__validate_health_strategy(const char *value)
 {
     return pcmk__strcase_any_of(value,
                                 PCMK__VALUE_NONE,
                                 PCMK__VALUE_CUSTOM,
                                 PCMK_VALUE_ONLY_GREEN,
-                                PCMK__VALUE_PROGRESSIVE,
+                                PCMK_VALUE_PROGRESSIVE,
                                 PCMK_VALUE_MIGRATE_ON_RED,
                                 NULL);
 }
 
 /*!
  * \internal
  * \brief Parse node health strategy from a user-provided string
  *
  * \param[in] value  User-provided configuration value for node-health-strategy
  *
  * \return Node health strategy corresponding to \p value
  */
 enum pcmk__health_strategy
 pcmk__parse_health_strategy(const char *value)
 {
     if (pcmk__str_eq(value, PCMK__VALUE_NONE,
                      pcmk__str_null_matches|pcmk__str_casei)) {
         return pcmk__health_strategy_none;
     }
     if (pcmk__str_eq(value, PCMK_VALUE_MIGRATE_ON_RED, pcmk__str_casei)) {
         return pcmk__health_strategy_no_red;
     }
     if (pcmk__str_eq(value, PCMK_VALUE_ONLY_GREEN, pcmk__str_casei)) {
         return pcmk__health_strategy_only_green;
-
-    } else if (pcmk__str_eq(value, PCMK__VALUE_PROGRESSIVE,
-                            pcmk__str_casei)) {
+    }
+    if (pcmk__str_eq(value, PCMK_VALUE_PROGRESSIVE, pcmk__str_casei)) {
         return pcmk__health_strategy_progressive;
 
     } else if (pcmk__str_eq(value, PCMK__VALUE_CUSTOM,
                             pcmk__str_casei)) {
         return pcmk__health_strategy_custom;
 
     } else {
         pcmk__config_err("Using default of \"" PCMK__VALUE_NONE "\" for "
                          PCMK_OPT_NODE_HEALTH_STRATEGY
                          " because '%s' is not a valid value",
                          value);
         return pcmk__health_strategy_none;
     }
 }
diff --git a/lib/common/options.c b/lib/common/options.c
index df09c8e445..13b4b600a9 100644
--- a/lib/common/options.c
+++ b/lib/common/options.c
@@ -1,1158 +1,1158 @@
 /*
  * 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/common/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
 }
 
 
 /*
  * Option metadata
  */
 
 static pcmk__cluster_option_t cluster_options[] = {
     /* name, old name, type, allowed values,
      * default value, validator,
      * context,
      * short description,
      * long description
      */
     {
         PCMK_OPT_DC_VERSION, NULL, "string", NULL,
         PCMK__VALUE_NONE, NULL,
         pcmk__opt_context_controld,
         N_("Pacemaker version on cluster node elected Designated Controller "
             "(DC)"),
         N_("Includes a hash which identifies the exact revision the code was "
             "built from. Used for diagnostic purposes."),
     },
     {
         PCMK_OPT_CLUSTER_INFRASTRUCTURE, NULL, "string", NULL,
         "corosync", NULL,
         pcmk__opt_context_controld,
         N_("The messaging layer on which Pacemaker is currently running"),
         N_("Used for informational and diagnostic purposes."),
     },
     {
         PCMK_OPT_CLUSTER_NAME, NULL, "string", NULL,
         NULL, NULL,
         pcmk__opt_context_controld,
         N_("An arbitrary name for the cluster"),
         N_("This optional value is mostly for users' convenience as desired "
             "in administration, but may also be used in Pacemaker "
             "configuration rules via the #cluster-name node attribute, and "
             "by higher-level tools and resource agents."),
     },
     {
         PCMK_OPT_DC_DEADTIME, NULL, "time", NULL,
         "20s", pcmk__valid_interval_spec,
         pcmk__opt_context_controld,
         N_("How long to wait for a response from other nodes during start-up"),
         N_("The optimal value will depend on the speed and load of your "
             "network and the type of switches used."),
     },
     {
         PCMK_OPT_CLUSTER_RECHECK_INTERVAL, NULL, "time",
         N_("Zero disables polling, while positive values are an interval in "
             "seconds (unless other units are specified, for example \"5min\")"),
         "15min", pcmk__valid_interval_spec,
         pcmk__opt_context_controld,
         N_("Polling interval to recheck cluster state and evaluate rules "
             "with date specifications"),
         N_("Pacemaker is primarily event-driven, and looks ahead to know when "
             "to recheck cluster state for failure-timeout settings and most "
             "time-based rules. However, it will also recheck the cluster after "
             "this amount of inactivity, to evaluate rules with date "
             "specifications and serve as a fail-safe for certain types of "
             "scheduler bugs."),
     },
     {
         PCMK_OPT_FENCE_REACTION, NULL, "select",
             PCMK_VALUE_STOP ", " PCMK_VALUE_PANIC,
         PCMK_VALUE_STOP, NULL,
         pcmk__opt_context_controld,
         N_("How a cluster node should react if notified of its own fencing"),
         N_("A cluster node may receive notification of a \"succeeded\" "
             "fencing that targeted it if fencing is misconfigured, or if "
             "fabric fencing is in use that doesn't cut cluster communication. "
             "Use \"stop\" to attempt to immediately stop Pacemaker and stay "
             "stopped, or \"panic\" to attempt to immediately reboot the local "
             "node, falling back to stop on failure."),
     },
     {
         PCMK_OPT_ELECTION_TIMEOUT, NULL, "time", NULL,
         "2min", pcmk__valid_interval_spec,
         pcmk__opt_context_controld,
         N_("*** Advanced Use Only ***"),
         N_("Declare an election failed if it is not decided within this much "
             "time. If you need to adjust this value, it probably indicates "
             "the presence of a bug."),
     },
     {
         PCMK_OPT_SHUTDOWN_ESCALATION, NULL, "time", NULL,
         "20min", pcmk__valid_interval_spec,
         pcmk__opt_context_controld,
         N_("*** Advanced Use Only ***"),
         N_("Exit immediately if shutdown does not complete within this much "
             "time. If you need to adjust this value, it probably indicates "
             "the presence of a bug."),
     },
     {
         PCMK_OPT_JOIN_INTEGRATION_TIMEOUT, "crmd-integration-timeout", "time",
             NULL,
         "3min", pcmk__valid_interval_spec,
         pcmk__opt_context_controld,
         N_("*** Advanced Use Only ***"),
         N_("If you need to adjust this value, it probably indicates "
             "the presence of a bug."),
     },
     {
         PCMK_OPT_JOIN_FINALIZATION_TIMEOUT, "crmd-finalization-timeout",
             "time", NULL,
         "30min", pcmk__valid_interval_spec,
         pcmk__opt_context_controld,
         N_("*** Advanced Use Only ***"),
         N_("If you need to adjust this value, it probably indicates "
             "the presence of a bug."),
     },
     {
         PCMK_OPT_TRANSITION_DELAY, "crmd-transition-delay", "time", NULL,
         "0s", pcmk__valid_interval_spec,
         pcmk__opt_context_controld,
         N_("*** Advanced Use Only *** "
             "Enabling this option will slow down cluster recovery under all "
             "conditions"),
         N_("Delay cluster recovery for this much time to allow for additional "
             "events to occur. Useful if your configuration is sensitive to "
             "the order in which ping updates arrive."),
     },
     {
         PCMK_OPT_NO_QUORUM_POLICY, NULL, "select",
             PCMK_VALUE_STOP ", " PCMK_VALUE_FREEZE ", " PCMK_VALUE_IGNORE
                 ", " PCMK_VALUE_DEMOTE ", " PCMK_VALUE_FENCE_LEGACY,
         PCMK_VALUE_STOP, pcmk__valid_no_quorum_policy,
         pcmk__opt_context_schedulerd,
         N_("What to do when the cluster does not have quorum"),
         NULL,
     },
     {
         PCMK_OPT_SHUTDOWN_LOCK, NULL, "boolean", NULL,
         PCMK_VALUE_FALSE, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("Whether to lock resources to a cleanly shut down node"),
         N_("When true, resources active on a node when it is cleanly shut down "
             "are kept \"locked\" to that node (not allowed to run elsewhere) "
             "until they start again on that node after it rejoins (or for at "
             "most shutdown-lock-limit, if set). Stonith resources and "
             "Pacemaker Remote connections are never locked. Clone and bundle "
             "instances and the promoted role of promotable clones are "
             "currently never locked, though support could be added in a future "
             "release."),
     },
     {
         PCMK_OPT_SHUTDOWN_LOCK_LIMIT, NULL, "time", NULL,
         "0", pcmk__valid_interval_spec,
         pcmk__opt_context_schedulerd,
         N_("Do not lock resources to a cleanly shut down node longer than "
            "this"),
         N_("If shutdown-lock is true and this is set to a nonzero time "
             "duration, shutdown locks will expire after this much time has "
             "passed since the shutdown was initiated, even if the node has not "
             "rejoined."),
     },
     {
         PCMK_OPT_ENABLE_ACL, NULL, "boolean", NULL,
         PCMK_VALUE_FALSE, pcmk__valid_boolean,
         pcmk__opt_context_based,
         N_("Enable Access Control Lists (ACLs) for the CIB"),
         NULL,
     },
     {
         PCMK_OPT_SYMMETRIC_CLUSTER, NULL, "boolean", NULL,
         PCMK_VALUE_TRUE, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("Whether resources can run on any node by default"),
         NULL,
     },
     {
         PCMK_OPT_MAINTENANCE_MODE, NULL, "boolean", NULL,
         PCMK_VALUE_FALSE, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("Whether the cluster should refrain from monitoring, starting, and "
             "stopping resources"),
         NULL,
     },
     {
         PCMK_OPT_START_FAILURE_IS_FATAL, NULL, "boolean", NULL,
         PCMK_VALUE_TRUE, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("Whether a start failure should prevent a resource from being "
             "recovered on the same node"),
         N_("When true, the cluster will immediately ban a resource from a node "
             "if it fails to start there. When false, the cluster will instead "
             "check the resource's fail count against its migration-threshold.")
     },
     {
         PCMK_OPT_ENABLE_STARTUP_PROBES, NULL, "boolean", NULL,
         PCMK_VALUE_TRUE, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("Whether the cluster should check for active resources during "
             "start-up"),
         NULL,
     },
 
     // Fencing-related options
     {
         PCMK_OPT_STONITH_ENABLED, NULL, "boolean", NULL,
         PCMK_VALUE_TRUE, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("*** Advanced Use Only *** "
             "Whether nodes may be fenced as part of recovery"),
         N_("If false, unresponsive nodes are immediately assumed to be "
             "harmless, and resources that were active on them may be recovered "
             "elsewhere. This can result in a \"split-brain\" situation, "
             "potentially leading to data loss and/or service unavailability."),
     },
     {
         PCMK_OPT_STONITH_ACTION, NULL, "select", "reboot, off, poweroff",
         PCMK_ACTION_REBOOT, pcmk__is_fencing_action,
         pcmk__opt_context_schedulerd,
         N_("Action to send to fence device when a node needs to be fenced "
             "(\"poweroff\" is a deprecated alias for \"off\")"),
         NULL,
     },
     {
         PCMK_OPT_STONITH_TIMEOUT, NULL, "time", NULL,
         "60s", pcmk__valid_interval_spec,
         pcmk__opt_context_schedulerd,
         N_("How long to wait for on, off, and reboot fence actions to complete "
             "by default"),
         NULL,
     },
     {
         PCMK_OPT_HAVE_WATCHDOG, NULL, "boolean", NULL,
         PCMK_VALUE_FALSE, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("Whether watchdog integration is enabled"),
         N_("This is set automatically by the cluster according to whether SBD "
             "is detected to be in use. User-configured values are ignored. "
             "The value `true` is meaningful if diskless SBD is used and "
             "`stonith-watchdog-timeout` is nonzero. In that case, if fencing "
             "is required, watchdog-based self-fencing will be performed via "
             "SBD without requiring a fencing resource explicitly configured."),
     },
     {
         /* @COMPAT Currently, unparsable values default to -1 (auto-calculate),
          * while missing values default to 0 (disable). All values are accepted
          * (unless the controller finds that the value conflicts with the
          * SBD_WATCHDOG_TIMEOUT).
          *
          * At a compatibility break: properly validate as a timeout, let
          * either negative values or a particular string like "auto" mean auto-
          * calculate, and use 0 as the single default for when the option either
          * is unset or fails to validate.
          */
         PCMK_OPT_STONITH_WATCHDOG_TIMEOUT, NULL, "time", NULL,
         "0", NULL,
         pcmk__opt_context_controld,
         N_("How long before nodes can be assumed to be safely down when "
            "watchdog-based self-fencing via SBD is in use"),
         N_("If this is set to a positive value, lost nodes are assumed to "
            "achieve self-fencing using watchdog-based SBD within this much "
            "time. This does not require a fencing resource to be explicitly "
            "configured, though a fence_watchdog resource can be configured, to "
            "limit use to specific nodes. If this is set to 0 (the default), "
            "the cluster will never assume watchdog-based self-fencing. If this "
            "is set to a negative value, the cluster will use twice the local "
            "value of the `SBD_WATCHDOG_TIMEOUT` environment variable if that "
            "is positive, or otherwise treat this as 0. WARNING: When used, "
            "this timeout must be larger than `SBD_WATCHDOG_TIMEOUT` on all "
            "nodes that use watchdog-based SBD, and Pacemaker will refuse to "
            "start on any of those nodes where this is not true for the local "
            "value or SBD is not active. When this is set to a negative value, "
            "`SBD_WATCHDOG_TIMEOUT` must be set to the same value on all nodes "
            "that use SBD, otherwise data corruption or loss could occur."),
     },
     {
         PCMK_OPT_STONITH_MAX_ATTEMPTS, NULL, "integer", NULL,
         "10", pcmk__valid_positive_int,
         pcmk__opt_context_controld,
         N_("How many times fencing can fail before it will no longer be "
             "immediately re-attempted on a target"),
         NULL,
     },
     {
         PCMK_OPT_CONCURRENT_FENCING, NULL, "boolean", NULL,
         PCMK__CONCURRENT_FENCING_DEFAULT, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("Allow performing fencing operations in parallel"),
         NULL,
     },
     {
         PCMK_OPT_STARTUP_FENCING, NULL, "boolean", NULL,
         PCMK_VALUE_TRUE, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("*** Advanced Use Only *** "
             "Whether to fence unseen nodes at start-up"),
         N_("Setting this to false may lead to a \"split-brain\" situation, "
             "potentially leading to data loss and/or service unavailability."),
     },
     {
         PCMK_OPT_PRIORITY_FENCING_DELAY, NULL, "time", NULL,
         "0", pcmk__valid_interval_spec,
         pcmk__opt_context_schedulerd,
         N_("Apply fencing delay targeting the lost nodes with the highest "
             "total resource priority"),
         N_("Apply specified delay for the fencings that are targeting the lost "
             "nodes with the highest total resource priority in case we don't "
             "have the majority of the nodes in our cluster partition, so that "
             "the more significant nodes potentially win any fencing match, "
             "which is especially meaningful under split-brain of 2-node "
             "cluster. A promoted resource instance takes the base priority + 1 "
             "on calculation if the base priority is not 0. Any static/random "
             "delays that are introduced by `pcmk_delay_base/max` configured "
             "for the corresponding fencing resources will be added to this "
             "delay. This delay should be significantly greater than, safely "
             "twice, the maximum `pcmk_delay_base/max`. By default, priority "
             "fencing delay is disabled."),
     },
     {
         PCMK_OPT_NODE_PENDING_TIMEOUT, NULL, "time", NULL,
         "0", pcmk__valid_interval_spec,
         pcmk__opt_context_schedulerd,
         N_("How long to wait for a node that has joined the cluster to join "
            "the controller process group"),
         N_("Fence nodes that do not join the controller process group within "
            "this much time after joining the cluster, to allow the cluster "
            "to continue managing resources. A value of 0 means never fence "
            "pending nodes. Setting the value to 2h means fence nodes after "
            "2 hours."),
     },
     {
         PCMK_OPT_CLUSTER_DELAY, NULL, "time", NULL,
         "60s", pcmk__valid_interval_spec,
         pcmk__opt_context_schedulerd,
         N_("Maximum time for node-to-node communication"),
         N_("The node elected Designated Controller (DC) will consider an action "
             "failed if it does not get a response from the node executing the "
             "action within this time (after considering the action's own "
             "timeout). The \"correct\" value will depend on the speed and "
             "load of your network and cluster nodes.")
     },
 
     // Limits
     {
         PCMK_OPT_LOAD_THRESHOLD, NULL, "percentage", NULL,
         "80%", pcmk__valid_percentage,
         pcmk__opt_context_controld,
         N_("Maximum amount of system load that should be used by cluster "
             "nodes"),
         N_("The cluster will slow down its recovery process when the amount of "
             "system resources used (currently CPU) approaches this limit"),
     },
     {
         PCMK_OPT_NODE_ACTION_LIMIT, NULL, "integer", NULL,
         "0", pcmk__valid_int,
         pcmk__opt_context_controld,
         N_("Maximum number of jobs that can be scheduled per node (defaults to "
             "2x cores)"),
         NULL,
     },
     {
         PCMK_OPT_BATCH_LIMIT, NULL, "integer", NULL,
         "0", pcmk__valid_int,
         pcmk__opt_context_schedulerd,
         N_("Maximum number of jobs that the cluster may execute in parallel "
             "across all nodes"),
         N_("The \"correct\" value will depend on the speed and load of your "
             "network and cluster nodes. If set to 0, the cluster will "
             "impose a dynamically calculated limit when any node has a "
             "high load."),
     },
     {
         PCMK_OPT_MIGRATION_LIMIT, NULL, "integer", NULL,
         "-1", pcmk__valid_int,
         pcmk__opt_context_schedulerd,
         N_("The number of live migration actions that the cluster is allowed "
             "to execute in parallel on a node (-1 means no limit)"),
         NULL,
     },
     {
         PCMK_OPT_CLUSTER_IPC_LIMIT, NULL, "integer", NULL,
         "500", pcmk__valid_positive_int,
         pcmk__opt_context_based,
         N_("Maximum IPC message backlog before disconnecting a cluster daemon"),
         N_("Raise this if log has \"Evicting client\" messages for cluster "
             "daemon PIDs (a good value is the number of resources in the "
             "cluster multiplied by the number of nodes)."),
     },
 
     // Orphans and stopping
     {
         PCMK_OPT_STOP_ALL_RESOURCES, NULL, "boolean", NULL,
         PCMK_VALUE_FALSE, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("Whether the cluster should stop all active resources"),
         NULL,
     },
     {
         PCMK_OPT_STOP_ORPHAN_RESOURCES, NULL, "boolean", NULL,
         PCMK_VALUE_TRUE, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("Whether to stop resources that were removed from the "
             "configuration"),
         NULL,
     },
     {
         PCMK_OPT_STOP_ORPHAN_ACTIONS, NULL, "boolean", NULL,
         PCMK_VALUE_TRUE, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("Whether to cancel recurring actions removed from the "
             "configuration"),
         NULL,
     },
     {
         PCMK__OPT_REMOVE_AFTER_STOP, NULL, "boolean", NULL,
         PCMK_VALUE_FALSE, pcmk__valid_boolean,
         pcmk__opt_context_schedulerd,
         N_("*** Deprecated *** "
             "Whether to remove stopped resources from the executor"),
         N_("Values other than default are poorly tested and potentially "
             "dangerous. This option will be removed in a future release."),
     },
 
     // Storing inputs
     {
         PCMK_OPT_PE_ERROR_SERIES_MAX, NULL, "integer", NULL,
         "-1", pcmk__valid_int,
         pcmk__opt_context_schedulerd,
         N_("The number of scheduler inputs resulting in errors to save"),
         N_("Zero to disable, -1 to store unlimited."),
     },
     {
         PCMK_OPT_PE_WARN_SERIES_MAX, NULL, "integer", NULL,
         "5000", pcmk__valid_int,
         pcmk__opt_context_schedulerd,
         N_("The number of scheduler inputs resulting in warnings to save"),
         N_("Zero to disable, -1 to store unlimited."),
     },
     {
         PCMK_OPT_PE_INPUT_SERIES_MAX, NULL, "integer", NULL,
         "4000", pcmk__valid_int,
         pcmk__opt_context_schedulerd,
         N_("The number of scheduler inputs without errors or warnings to save"),
         N_("Zero to disable, -1 to store unlimited."),
     },
 
     // Node health
     {
         PCMK_OPT_NODE_HEALTH_STRATEGY, NULL, "select",
             PCMK__VALUE_NONE ", " PCMK_VALUE_MIGRATE_ON_RED ", "
-                PCMK_VALUE_ONLY_GREEN ", " PCMK__VALUE_PROGRESSIVE ", "
+                PCMK_VALUE_ONLY_GREEN ", " PCMK_VALUE_PROGRESSIVE ", "
                 PCMK__VALUE_CUSTOM,
         PCMK__VALUE_NONE, pcmk__validate_health_strategy,
         pcmk__opt_context_schedulerd,
         N_("How cluster should react to node health attributes"),
         N_("Requires external entities to create node attributes (named with "
             "the prefix \"#health\") with values \"red\", \"yellow\", or "
             "\"green\".")
     },
     {
         PCMK_OPT_NODE_HEALTH_BASE, NULL, "integer", NULL,
         "0", pcmk__valid_int,
         pcmk__opt_context_schedulerd,
         N_("Base health score assigned to a node"),
         N_("Only used when \"node-health-strategy\" is set to "
             "\"progressive\"."),
     },
     {
         PCMK_OPT_NODE_HEALTH_GREEN, NULL, "integer", NULL,
         "0", pcmk__valid_int,
         pcmk__opt_context_schedulerd,
         N_("The score to use for a node health attribute whose value is "
             "\"green\""),
         N_("Only used when \"node-health-strategy\" is set to \"custom\" or "
             "\"progressive\"."),
     },
     {
         PCMK_OPT_NODE_HEALTH_YELLOW, NULL, "integer", NULL,
         "0", pcmk__valid_int,
         pcmk__opt_context_schedulerd,
         N_("The score to use for a node health attribute whose value is "
             "\"yellow\""),
         N_("Only used when \"node-health-strategy\" is set to \"custom\" or "
             "\"progressive\"."),
     },
     {
         PCMK_OPT_NODE_HEALTH_RED, NULL, "integer", NULL,
         "-INFINITY", pcmk__valid_int,
         pcmk__opt_context_schedulerd,
         N_("The score to use for a node health attribute whose value is "
             "\"red\""),
         N_("Only used when \"node-health-strategy\" is set to \"custom\" or "
             "\"progressive\".")
     },
 
     // Placement strategy
     {
         PCMK_OPT_PLACEMENT_STRATEGY, NULL, "select",
             PCMK_VALUE_DEFAULT ", " PCMK_VALUE_UTILIZATION ", "
                 PCMK_VALUE_MINIMAL ", " PCMK_VALUE_BALANCED,
         PCMK_VALUE_DEFAULT, pcmk__valid_placement_strategy,
         pcmk__opt_context_schedulerd,
         N_("How the cluster should allocate resources to nodes"),
         NULL,
     },
 };
 
 
 /*
  * 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,
                                 PCMK_VALUE_STOP, PCMK_VALUE_FREEZE,
                                 PCMK_VALUE_IGNORE, PCMK_VALUE_DEMOTE,
                                 PCMK_VALUE_FENCE_LEGACY, 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 whether a string represents a valid placement strategy
  *
  * \param[in] value  String to validate
  *
  * \return \c true if \p value is a valid placement strategy, or \c false
  *         otherwise
  */
 bool
 pcmk__valid_placement_strategy(const char *value)
 {
     return pcmk__strcase_any_of(value,
                                 PCMK_VALUE_DEFAULT, PCMK_VALUE_UTILIZATION,
                                 PCMK_VALUE_MINIMAL, PCMK_VALUE_BALANCED, NULL);
 }
 
 /*!
  * \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]     name     (Primary) option name to look for
  *
  * \return Option value
  */
 const char *
 pcmk__cluster_option(GHashTable *options, const char *name)
 {
     for (int lpc = 0; lpc < PCMK__NELEM(cluster_options); lpc++) {
         if (pcmk__str_eq(name, cluster_options[lpc].name, pcmk__str_casei)) {
             return cluster_option_value(options,
                                         cluster_options[lpc].is_valid,
                                         cluster_options[lpc].name,
                                         cluster_options[lpc].alt_name,
                                         cluster_options[lpc].default_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 "=\"" 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);
 }
 
 /*!
  * \internal
  * \brief Format option metadata as an OCF-like XML string
  *
  * \param[in] name         Daemon name
  * \param[in] desc_short   Short description of the daemon
  * \param[in] desc_long    Long description of the daemon
  * \param[in] filter       If not \c pcmk__opt_context_none, include only
  *                         those options whose \c context field is equal to
  *                         \p filter
  * \param[in] option_list  Options whose metadata to format
  * \param[in] len          Number of items in \p option_list
  *
  * \return A string containing OCF-like option metadata XML
  *
  * \note The caller is responsible for freeing the return value using
  *       \c g_free().
  */
 gchar *
 pcmk__format_option_metadata(const char *name, const char *desc_short,
                              const char *desc_long,
                              enum pcmk__opt_context filter,
                              pcmk__cluster_option_t *option_list, int len)
 {
     // Large enough to hold current cluster options with room for growth (2^15)
     GString *s = g_string_sized_new(32768);
 
     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;
 
         if ((filter != pcmk__opt_context_none)
             && (filter != option_list[lpc].context)) {
             continue;
         }
 
         // 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, "      <" PCMK_XE_CONTENT " "
                                     PCMK_XA_TYPE "=\"", opt_type, "\"", NULL);
         if (opt_default != NULL) {
             pcmk__g_strcat(s,
                            " " PCMK_XA_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,
                                "        <" PCMK_XE_OPTION " "
                                            PCMK_XA_VALUE "=\"", ptr, "\" />\n",
                                NULL);
                 ptr = strtok(NULL, delim);
             }
             g_string_append(s, "      </" PCMK_XE_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);
 }
 
 /*!
  * \internal
  * \brief Format cluster option metadata as an OCF-like XML string
  *
  * \param[in] name        Daemon name
  * \param[in] desc_short  Short description of the daemon
  * \param[in] desc_long   Long description of the daemon
  * \param[in] filter      If not \c pcmk__opt_context_none, include only
  *                        those options whose \c context field is equal to
  *                        \p filter
  *
  * \return A string containing OCF-like cluster option metadata XML
  *
  * \note The caller is responsible for freeing the return value using
  *       \c g_free().
  */
 gchar *
 pcmk__cluster_option_metadata(const char *name, const char *desc_short,
                               const char *desc_long,
                               enum pcmk__opt_context filter)
 {
     return pcmk__format_option_metadata(name, desc_short, desc_long, filter,
                                         cluster_options,
                                         PCMK__NELEM(cluster_options));
 }
 
 void
 pcmk__validate_cluster_options(GHashTable *options)
 {
     for (int lpc = 0; lpc < PCMK__NELEM(cluster_options); lpc++) {
         cluster_option_value(options,
                              cluster_options[lpc].is_valid,
                              cluster_options[lpc].name,
                              cluster_options[lpc].alt_name,
                              cluster_options[lpc].default_value);
     }
 }