diff --git a/cts/cli/regression.error_codes.exp b/cts/cli/regression.error_codes.exp
index f8e6b95233..884add88dc 100644
--- a/cts/cli/regression.error_codes.exp
+++ b/cts/cli/regression.error_codes.exp
@@ -1,572 +1,576 @@
=#=#=#= Begin test: Get legacy return code =#=#=#=
Error
=#=#=#= End test: Get legacy return code - OK (0) =#=#=#=
* Passed: crm_error - Get legacy return code
=#=#=#= Begin test: Get legacy return code (XML) =#=#=#=
=#=#=#= End test: Get legacy return code (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get legacy return code (XML)
=#=#=#= Begin test: Get legacy return code (with name) =#=#=#=
pcmk_err_generic - Error
=#=#=#= End test: Get legacy return code (with name) - OK (0) =#=#=#=
* Passed: crm_error - Get legacy return code (with name)
=#=#=#= Begin test: Get legacy return code (with name) (XML) =#=#=#=
=#=#=#= End test: Get legacy return code (with name) (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get legacy return code (with name) (XML)
=#=#=#= Begin test: Get multiple legacy return codes =#=#=#=
Error
Operation requires quorum
=#=#=#= End test: Get multiple legacy return codes - OK (0) =#=#=#=
* Passed: crm_error - Get multiple legacy return codes
=#=#=#= Begin test: Get multiple legacy return codes (XML) =#=#=#=
=#=#=#= End test: Get multiple legacy return codes (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get multiple legacy return codes (XML)
=#=#=#= Begin test: Get multiple legacy return codes (with names) =#=#=#=
pcmk_err_generic - Error
pcmk_err_no_quorum - Operation requires quorum
=#=#=#= End test: Get multiple legacy return codes (with names) - OK (0) =#=#=#=
* Passed: crm_error - Get multiple legacy return codes (with names)
=#=#=#= Begin test: Get multiple legacy return codes (with names) (XML) =#=#=#=
=#=#=#= End test: Get multiple legacy return codes (with names) (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get multiple legacy return codes (with names) (XML)
=#=#=#= Begin test: List legacy return codes (spot check) =#=#=#=
201: Error
202: Operation requires quorum
203: Update does not conform to the configured schema
204: Schema transform failed
205: Update was older than existing configuration
206: Application of update diff failed
207: Application of update diff failed, requesting full refresh
208: On-disk configuration was manually modified
209: Could not archive previous configuration
=#=#=#= End test: List legacy return codes (spot check) - OK (0) =#=#=#=
* Passed: crm_error - List legacy return codes (spot check)
=#=#=#= Begin test: List legacy return codes (spot check) (XML) =#=#=#=
=#=#=#= End test: List legacy return codes (spot check) (XML) - OK (0) =#=#=#=
* Passed: crm_error - List legacy return codes (spot check) (XML)
=#=#=#= Begin test: List legacy return codes (spot check) (with names) =#=#=#=
201: pcmk_err_generic Error
202: pcmk_err_no_quorum Operation requires quorum
203: pcmk_err_schema_validation Update does not conform to the configured schema
204: pcmk_err_transform_failed Schema transform failed
205: pcmk_err_old_data Update was older than existing configuration
206: pcmk_err_diff_failed Application of update diff failed
207: pcmk_err_diff_resync Application of update diff failed, requesting full refresh
208: pcmk_err_cib_modified On-disk configuration was manually modified
209: pcmk_err_cib_backup Could not archive previous configuration
=#=#=#= End test: List legacy return codes (spot check) (with names) - OK (0) =#=#=#=
* Passed: crm_error - List legacy return codes (spot check) (with names)
=#=#=#= Begin test: List legacy return codes (spot check) (with names) (XML) =#=#=#=
=#=#=#= End test: List legacy return codes (spot check) (with names) (XML) - OK (0) =#=#=#=
* Passed: crm_error - List legacy return codes (spot check) (with names) (XML)
=#=#=#= Begin test: Get unknown Pacemaker return code =#=#=#=
Error
=#=#=#= End test: Get unknown Pacemaker return code - OK (0) =#=#=#=
* Passed: crm_error - Get unknown Pacemaker return code
=#=#=#= Begin test: Get unknown Pacemaker return code (XML) =#=#=#=
=#=#=#= End test: Get unknown Pacemaker return code (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get unknown Pacemaker return code (XML)
=#=#=#= Begin test: Get unknown Pacemaker return code (with name) =#=#=#=
Unknown - Error
=#=#=#= End test: Get unknown Pacemaker return code (with name) - OK (0) =#=#=#=
* Passed: crm_error - Get unknown Pacemaker return code (with name)
=#=#=#= Begin test: Get unknown Pacemaker return code (with name) (XML) =#=#=#=
=#=#=#= End test: Get unknown Pacemaker return code (with name) (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get unknown Pacemaker return code (with name) (XML)
=#=#=#= Begin test: Get negative Pacemaker return code =#=#=#=
Node not found
=#=#=#= End test: Get negative Pacemaker return code - OK (0) =#=#=#=
* Passed: crm_error - Get negative Pacemaker return code
=#=#=#= Begin test: Get negative Pacemaker return code (XML) =#=#=#=
=#=#=#= End test: Get negative Pacemaker return code (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get negative Pacemaker return code (XML)
=#=#=#= Begin test: Get negative Pacemaker return code (with name) =#=#=#=
pcmk_rc_node_unknown - Node not found
=#=#=#= End test: Get negative Pacemaker return code (with name) - OK (0) =#=#=#=
* Passed: crm_error - Get negative Pacemaker return code (with name)
=#=#=#= Begin test: Get negative Pacemaker return code (with name) (XML) =#=#=#=
=#=#=#= End test: Get negative Pacemaker return code (with name) (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get negative Pacemaker return code (with name) (XML)
=#=#=#= Begin test: List Pacemaker return codes (non-positive) =#=#=#=
+-1043: Digest does not match expected value
-1042: Internal corosync error
-1041: More IPC message fragments to send
-1040: DC is not yet elected
-1039: Compression/decompression error
-1038: Nameserver resolution error
-1037: No active transaction found
-1036: Bad XML patch format
-1035: Bad input value provided
-1034: Disabled
-1033: Two or more XML elements have the same ID
-1032: Unable to parse CIB XML
-1031: Cluster simulation produced invalid transition
-1030: Error writing graph file
-1029: Error writing dot(1) file
-1028: Value too small to be stored in data type
-1027: Input file not available
-1026: Output message produced no output
-1025: Result occurs after given range
-1024: Result occurs within given range
-1023: Result occurs before given range
-1022: Result undetermined
-1021: Not applicable under current conditions
-1020: IPC server process is active but not accepting connections
-1019: IPC server is unresponsive
-1018: IPC server is blocked by unauthorized process
-1017: Operation requires quorum
-1016: Update does not conform to the configured schema
-1015: Schema is already the latest available
-1014: Schema transform failed
-1013: Update was older than existing configuration
-1012: Application of update diff failed
-1011: Application of update diff failed, requesting full refresh
-1010: On-disk configuration was manually modified
-1009: Could not archive previous configuration
-1008: Could not save new configuration to disk
-1007: Could not parse on-disk configuration
-1006: Resource active on multiple nodes
-1005: Node not found
-1004: Already in requested state
-1003: Bad name/value pair given
-1002: Unknown output format
-1001: Error
0: OK
=#=#=#= End test: List Pacemaker return codes (non-positive) - OK (0) =#=#=#=
* Passed: crm_error - List Pacemaker return codes (non-positive)
=#=#=#= Begin test: List Pacemaker return codes (non-positive) (XML) =#=#=#=
+
=#=#=#= End test: List Pacemaker return codes (non-positive) (XML) - OK (0) =#=#=#=
* Passed: crm_error - List Pacemaker return codes (non-positive) (XML)
=#=#=#= Begin test: List Pacemaker return codes (non-positive) (with names) =#=#=#=
+-1043: pcmk_rc_digest_mismatch Digest does not match expected value
-1042: pcmk_rc_cs_internal Internal corosync error
-1041: pcmk_rc_ipc_more More IPC message fragments to send
-1040: pcmk_rc_no_dc DC is not yet elected
-1039: pcmk_rc_compression Compression/decompression error
-1038: pcmk_rc_ns_resolution Nameserver resolution error
-1037: pcmk_rc_no_transaction No active transaction found
-1036: pcmk_rc_bad_xml_patch Bad XML patch format
-1035: pcmk_rc_bad_input Bad input value provided
-1034: pcmk_rc_disabled Disabled
-1033: pcmk_rc_duplicate_id Two or more XML elements have the same ID
-1032: pcmk_rc_unpack_error Unable to parse CIB XML
-1031: pcmk_rc_invalid_transition Cluster simulation produced invalid transition
-1030: pcmk_rc_graph_error Error writing graph file
-1029: pcmk_rc_dot_error Error writing dot(1) file
-1028: pcmk_rc_underflow Value too small to be stored in data type
-1027: pcmk_rc_no_input Input file not available
-1026: pcmk_rc_no_output Output message produced no output
-1025: pcmk_rc_after_range Result occurs after given range
-1024: pcmk_rc_within_range Result occurs within given range
-1023: pcmk_rc_before_range Result occurs before given range
-1022: pcmk_rc_undetermined Result undetermined
-1021: pcmk_rc_op_unsatisfied Not applicable under current conditions
-1020: pcmk_rc_ipc_pid_only IPC server process is active but not accepting connections
-1019: pcmk_rc_ipc_unresponsive IPC server is unresponsive
-1018: pcmk_rc_ipc_unauthorized IPC server is blocked by unauthorized process
-1017: pcmk_rc_no_quorum Operation requires quorum
-1016: pcmk_rc_schema_validation Update does not conform to the configured schema
-1015: pcmk_rc_schema_unchanged Schema is already the latest available
-1014: pcmk_rc_transform_failed Schema transform failed
-1013: pcmk_rc_old_data Update was older than existing configuration
-1012: pcmk_rc_diff_failed Application of update diff failed
-1011: pcmk_rc_diff_resync Application of update diff failed, requesting full refresh
-1010: pcmk_rc_cib_modified On-disk configuration was manually modified
-1009: pcmk_rc_cib_backup Could not archive previous configuration
-1008: pcmk_rc_cib_save Could not save new configuration to disk
-1007: pcmk_rc_cib_corrupt Could not parse on-disk configuration
-1006: pcmk_rc_multiple Resource active on multiple nodes
-1005: pcmk_rc_node_unknown Node not found
-1004: pcmk_rc_already Already in requested state
-1003: pcmk_rc_bad_nvpair Bad name/value pair given
-1002: pcmk_rc_unknown_format Unknown output format
-1001: pcmk_rc_error Error
0: pcmk_rc_ok OK
=#=#=#= End test: List Pacemaker return codes (non-positive) (with names) - OK (0) =#=#=#=
* Passed: crm_error - List Pacemaker return codes (non-positive) (with names)
=#=#=#= Begin test: List Pacemaker return codes (non-positive) (with names) (XML) =#=#=#=
+
=#=#=#= End test: List Pacemaker return codes (non-positive) (with names) (XML) - OK (0) =#=#=#=
* Passed: crm_error - List Pacemaker return codes (non-positive) (with names) (XML)
=#=#=#= Begin test: Get unknown crm_exit_t exit code =#=#=#=
Unknown exit status
=#=#=#= End test: Get unknown crm_exit_t exit code - OK (0) =#=#=#=
* Passed: crm_error - Get unknown crm_exit_t exit code
=#=#=#= Begin test: Get unknown crm_exit_t exit code (XML) =#=#=#=
=#=#=#= End test: Get unknown crm_exit_t exit code (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get unknown crm_exit_t exit code (XML)
=#=#=#= Begin test: Get unknown crm_exit_t exit code (with name) =#=#=#=
CRM_EX_UNKNOWN - Unknown exit status
=#=#=#= End test: Get unknown crm_exit_t exit code (with name) - OK (0) =#=#=#=
* Passed: crm_error - Get unknown crm_exit_t exit code (with name)
=#=#=#= Begin test: Get unknown crm_exit_t exit code (with name) (XML) =#=#=#=
=#=#=#= End test: Get unknown crm_exit_t exit code (with name) (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get unknown crm_exit_t exit code (with name) (XML)
=#=#=#= Begin test: Get crm_exit_t exit code =#=#=#=
Error occurred
=#=#=#= End test: Get crm_exit_t exit code - OK (0) =#=#=#=
* Passed: crm_error - Get crm_exit_t exit code
=#=#=#= Begin test: Get crm_exit_t exit code (XML) =#=#=#=
=#=#=#= End test: Get crm_exit_t exit code (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get crm_exit_t exit code (XML)
=#=#=#= Begin test: Get crm_exit_t exit code (with name) =#=#=#=
CRM_EX_ERROR - Error occurred
=#=#=#= End test: Get crm_exit_t exit code (with name) - OK (0) =#=#=#=
* Passed: crm_error - Get crm_exit_t exit code (with name)
=#=#=#= Begin test: Get crm_exit_t exit code (with name) (XML) =#=#=#=
=#=#=#= End test: Get crm_exit_t exit code (with name) (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get crm_exit_t exit code (with name) (XML)
=#=#=#= Begin test: Get all crm_exit_t exit codes =#=#=#=
0: OK
1: Error occurred
2: Invalid parameter
3: Unimplemented
4: Insufficient privileges
5: Not installed
6: Not configured
7: Not running
8: Promoted
9: Failed in promoted role
64: Incorrect usage
65: Invalid data given
66: Input file not available
67: User does not exist
68: Host does not exist
69: Necessary service unavailable
70: Internal software bug
71: Operating system error occurred
72: System file not available
73: Cannot create output file
74: I/O error occurred
75: Temporary failure, try again
76: Protocol violated
77: Insufficient privileges
78: Invalid configuration
100: Fatal error occurred, will not respawn
101: System panic required
102: Not connected
103: Update was older than existing configuration
104: Digest mismatch
105: No such object
106: Quorum required
107: Operation not safe
108: Requested item already exists
109: Multiple items match request
110: Requested item has expired
111: Requested item is not yet in effect
112: Could not determine status
113: Not applicable under current conditions
114: DC is not yet elected
124: Timeout occurred
190: Service is active but might fail soon
191: Service is promoted but might fail soon
193: No exit status available
=#=#=#= End test: Get all crm_exit_t exit codes - OK (0) =#=#=#=
* Passed: crm_error - Get all crm_exit_t exit codes
=#=#=#= Begin test: Get all crm_exit_t exit codes (XML) =#=#=#=
=#=#=#= End test: Get all crm_exit_t exit codes (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get all crm_exit_t exit codes (XML)
=#=#=#= Begin test: Get all crm_exit_t exit codes (with name) =#=#=#=
0: CRM_EX_OK OK
1: CRM_EX_ERROR Error occurred
2: CRM_EX_INVALID_PARAM Invalid parameter
3: CRM_EX_UNIMPLEMENT_FEATURE Unimplemented
4: CRM_EX_INSUFFICIENT_PRIV Insufficient privileges
5: CRM_EX_NOT_INSTALLED Not installed
6: CRM_EX_NOT_CONFIGURED Not configured
7: CRM_EX_NOT_RUNNING Not running
8: CRM_EX_PROMOTED Promoted
9: CRM_EX_FAILED_PROMOTED Failed in promoted role
64: CRM_EX_USAGE Incorrect usage
65: CRM_EX_DATAERR Invalid data given
66: CRM_EX_NOINPUT Input file not available
67: CRM_EX_NOUSER User does not exist
68: CRM_EX_NOHOST Host does not exist
69: CRM_EX_UNAVAILABLE Necessary service unavailable
70: CRM_EX_SOFTWARE Internal software bug
71: CRM_EX_OSERR Operating system error occurred
72: CRM_EX_OSFILE System file not available
73: CRM_EX_CANTCREAT Cannot create output file
74: CRM_EX_IOERR I/O error occurred
75: CRM_EX_TEMPFAIL Temporary failure, try again
76: CRM_EX_PROTOCOL Protocol violated
77: CRM_EX_NOPERM Insufficient privileges
78: CRM_EX_CONFIG Invalid configuration
100: CRM_EX_FATAL Fatal error occurred, will not respawn
101: CRM_EX_PANIC System panic required
102: CRM_EX_DISCONNECT Not connected
103: CRM_EX_OLD Update was older than existing configuration
104: CRM_EX_DIGEST Digest mismatch
105: CRM_EX_NOSUCH No such object
106: CRM_EX_QUORUM Quorum required
107: CRM_EX_UNSAFE Operation not safe
108: CRM_EX_EXISTS Requested item already exists
109: CRM_EX_MULTIPLE Multiple items match request
110: CRM_EX_EXPIRED Requested item has expired
111: CRM_EX_NOT_YET_IN_EFFECT Requested item is not yet in effect
112: CRM_EX_INDETERMINATE Could not determine status
113: CRM_EX_UNSATISFIED Not applicable under current conditions
114: CRM_EX_NO_DC DC is not yet elected
124: CRM_EX_TIMEOUT Timeout occurred
190: CRM_EX_DEGRADED Service is active but might fail soon
191: CRM_EX_DEGRADED_PROMOTED Service is promoted but might fail soon
193: CRM_EX_NONE No exit status available
=#=#=#= End test: Get all crm_exit_t exit codes (with name) - OK (0) =#=#=#=
* Passed: crm_error - Get all crm_exit_t exit codes (with name)
=#=#=#= Begin test: Get all crm_exit_t exit codes (with name) (XML) =#=#=#=
=#=#=#= End test: Get all crm_exit_t exit codes (with name) (XML) - OK (0) =#=#=#=
* Passed: crm_error - Get all crm_exit_t exit codes (with name) (XML)
diff --git a/include/crm/common/results.h b/include/crm/common/results.h
index d220ea55f6..7c7126fd0b 100644
--- a/include/crm/common/results.h
+++ b/include/crm/common/results.h
@@ -1,405 +1,406 @@
/*
* Copyright 2012-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__CRM_COMMON_RESULTS__H
#define PCMK__CRM_COMMON_RESULTS__H
#include // gboolean
#ifdef __cplusplus
extern "C" {
#endif
/*!
* \file
* \brief Function and executable result codes
* \ingroup core
*/
// Lifted from config.h
/* The _Noreturn keyword of C11. */
#ifndef _Noreturn
# if (defined __cplusplus \
&& ((201103 <= __cplusplus && !(__GNUC__ == 4 && __GNUC_MINOR__ == 7)) \
|| (defined _MSC_VER && 1900 <= _MSC_VER)))
# define _Noreturn [[noreturn]]
# elif ((!defined __cplusplus || defined __clang__) \
&& (201112 <= (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) \
|| 4 < __GNUC__ + (7 <= __GNUC_MINOR__)))
/* _Noreturn works as-is. */
# elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) || 0x5110 <= __SUNPRO_C
# define _Noreturn __attribute__ ((__noreturn__))
# elif 1200 <= (defined _MSC_VER ? _MSC_VER : 0)
# define _Noreturn __declspec (noreturn)
# else
# define _Noreturn
# endif
#endif
/*
* Function return codes
*
* Most Pacemaker API functions return an integer return code. There are two
* alternative interpretations. The legacy interpration is that the absolute
* value of the return code is either a system error number or a custom
* pcmk_err_* number. This is less than ideal because system error numbers are
* constrained only to the positive int range, so there's the possibility that
* system errors and custom errors could collide (which did in fact happen
* already on one architecture). The new intepretation is that negative values
* are from the pcmk_rc_e enum, and positive values are system error numbers.
* Both use 0 for success.
*
* For system error codes, see:
* - /usr/include/asm-generic/errno.h
* - /usr/include/asm-generic/errno-base.h
*/
// Legacy custom return codes for Pacemaker API functions (deprecated)
// NOTE: sbd (as of at least 1.5.2) uses this
#define pcmk_ok 0
#define PCMK_ERROR_OFFSET 190 /* Replacements on non-linux systems, see include/portability.h */
#define PCMK_CUSTOM_OFFSET 200 /* Purely custom codes */
#define pcmk_err_generic 201
#define pcmk_err_no_quorum 202
#define pcmk_err_schema_validation 203
#define pcmk_err_transform_failed 204
#define pcmk_err_old_data 205
// NOTE: sbd (as of at least 1.5.2) uses this
#define pcmk_err_diff_failed 206
// NOTE: sbd (as of at least 1.5.2) uses this
#define pcmk_err_diff_resync 207
#define pcmk_err_cib_modified 208
#define pcmk_err_cib_backup 209
#define pcmk_err_cib_save 210
#define pcmk_err_schema_unchanged 211
#define pcmk_err_cib_corrupt 212
#define pcmk_err_multiple 213
#define pcmk_err_node_unknown 214
#define pcmk_err_already 215
/* On HPPA 215 is ENOSYM (Unknown error 215), which hopefully never happens. */
#ifdef __hppa__
#define pcmk_err_bad_nvpair 250 /* 216 is ENOTSOCK */
#define pcmk_err_unknown_format 252 /* 217 is EDESTADDRREQ */
#else
#define pcmk_err_bad_nvpair 216
#define pcmk_err_unknown_format 217
#endif
/*!
* \brief Return codes for Pacemaker API functions
*
* Any Pacemaker API function documented as returning a "standard Pacemaker
* return code" will return pcmk_rc_ok (0) on success, and one of this
* enumeration's other (negative) values or a (positive) system error number
* otherwise. The custom codes are at -1001 and lower, so that the caller may
* use -1 through -1000 for their own custom values if desired. While generally
* referred to as "errors", nonzero values simply indicate a result, which might
* or might not be an error depending on the calling context.
*/
enum pcmk_rc_e {
/* When adding new values, use consecutively lower numbers, update the array
* in lib/common/results.c, and test with crm_error.
*/
+ pcmk_rc_digest_mismatch = -1043,
pcmk_rc_cs_internal = -1042,
pcmk_rc_ipc_more = -1041,
pcmk_rc_no_dc = -1040,
pcmk_rc_compression = -1039,
pcmk_rc_ns_resolution = -1038,
pcmk_rc_no_transaction = -1037,
pcmk_rc_bad_xml_patch = -1036,
pcmk_rc_bad_input = -1035,
pcmk_rc_disabled = -1034,
pcmk_rc_duplicate_id = -1033,
pcmk_rc_unpack_error = -1032,
pcmk_rc_invalid_transition = -1031,
pcmk_rc_graph_error = -1030,
pcmk_rc_dot_error = -1029,
pcmk_rc_underflow = -1028,
pcmk_rc_no_input = -1027,
pcmk_rc_no_output = -1026,
pcmk_rc_after_range = -1025,
pcmk_rc_within_range = -1024,
pcmk_rc_before_range = -1023,
pcmk_rc_undetermined = -1022,
pcmk_rc_op_unsatisfied = -1021,
pcmk_rc_ipc_pid_only = -1020,
pcmk_rc_ipc_unresponsive = -1019,
pcmk_rc_ipc_unauthorized = -1018,
pcmk_rc_no_quorum = -1017,
pcmk_rc_schema_validation = -1016,
pcmk_rc_schema_unchanged = -1015,
pcmk_rc_transform_failed = -1014,
pcmk_rc_old_data = -1013,
pcmk_rc_diff_failed = -1012,
pcmk_rc_diff_resync = -1011,
pcmk_rc_cib_modified = -1010,
pcmk_rc_cib_backup = -1009,
pcmk_rc_cib_save = -1008,
pcmk_rc_cib_corrupt = -1007,
pcmk_rc_multiple = -1006,
pcmk_rc_node_unknown = -1005,
pcmk_rc_already = -1004,
pcmk_rc_bad_nvpair = -1003,
pcmk_rc_unknown_format = -1002,
// Developers: Use a more specific code than pcmk_rc_error whenever possible
pcmk_rc_error = -1001,
// Values -1 through -1000 reserved for caller use
// NOTE: sbd (as of at least 1.5.2) uses this
pcmk_rc_ok = 0
// Positive values reserved for system error numbers
};
/*!
* \brief Exit status codes for resource agents
*
* The OCF Resource Agent API standard enumerates the possible exit status codes
* that agents should return. Besides being used with OCF agents, these values
* are also used by the executor as a universal status for all agent standards;
* actual results are mapped to these before returning them to clients.
*/
enum ocf_exitcode {
PCMK_OCF_OK = 0, //!< Success
// NOTE: booth (as of at least 1.1) uses this value
PCMK_OCF_UNKNOWN_ERROR = 1, //!< Unspecified error
PCMK_OCF_INVALID_PARAM = 2, //!< Parameter invalid (in local context)
PCMK_OCF_UNIMPLEMENT_FEATURE = 3, //!< Requested action not implemented
PCMK_OCF_INSUFFICIENT_PRIV = 4, //!< Insufficient privileges
PCMK_OCF_NOT_INSTALLED = 5, //!< Dependencies not available locally
PCMK_OCF_NOT_CONFIGURED = 6, //!< Parameter invalid (inherently)
// NOTE: booth (as of at least 1.1) uses this value
PCMK_OCF_NOT_RUNNING = 7, //!< Service safely stopped
PCMK_OCF_RUNNING_PROMOTED = 8, //!< Service active and promoted
PCMK_OCF_FAILED_PROMOTED = 9, //!< Service failed and possibly in promoted role
PCMK_OCF_DEGRADED = 190, //!< Service active but more likely to fail soon
PCMK_OCF_DEGRADED_PROMOTED = 191, //!< Service promoted but more likely to fail soon
/* These two are Pacemaker extensions, not in the OCF standard. The
* controller records PCMK_OCF_UNKNOWN for pending actions.
* PCMK_OCF_CONNECTION_DIED is used only with older DCs that don't support
* PCMK_EXEC_NOT_CONNECTED.
*/
PCMK_OCF_CONNECTION_DIED = 189, //!< \deprecated See PCMK_EXEC_NOT_CONNECTED
PCMK_OCF_UNKNOWN = 193, //!< Action is pending
};
// NOTE: sbd (as of at least 1.5.2) uses this
/*!
* \brief Exit status codes for tools and daemons
*
* We want well-specified (i.e. OS-invariant) exit status codes for our daemons
* and applications so they can be relied on by callers. (Function return codes
* and errno's do not make good exit statuses.)
*
* The only hard rule is that exit statuses must be between 0 and 255; all else
* is convention. Universally, 0 is success, and 1 is generic error (excluding
* OSes we don't support -- for example, OpenVMS considers 1 success!).
*
* For init scripts, the LSB gives meaning to 0-7, and sets aside 150-199 for
* application use. OCF adds 8-9 and 190-191.
*
* sysexits.h was an attempt to give additional meanings, but never really
* caught on. It uses 0 and 64-78.
*
* Bash reserves 2 ("incorrect builtin usage") and 126-255 (126 is "command
* found but not executable", 127 is "command not found", 128 + n is
* "interrupted by signal n").
*
* tldp.org recommends 64-113 for application use.
*
* We try to overlap with the above conventions when practical.
*
* NOTE: When new exit codes are added here, remember to also update
* python/pacemaker/exitstatus.py.
*/
typedef enum crm_exit_e {
// Common convention
CRM_EX_OK = 0, //!< Success
CRM_EX_ERROR = 1, //!< Unspecified error
// LSB + OCF
CRM_EX_INVALID_PARAM = 2, //!< Parameter invalid (in local context)
CRM_EX_UNIMPLEMENT_FEATURE = 3, //!< Requested action not implemented
CRM_EX_INSUFFICIENT_PRIV = 4, //!< Insufficient privileges
CRM_EX_NOT_INSTALLED = 5, //!< Dependencies not available locally
CRM_EX_NOT_CONFIGURED = 6, //!< Parameter invalid (inherently)
CRM_EX_NOT_RUNNING = 7, //!< Service safely stopped
CRM_EX_PROMOTED = 8, //!< Service active and promoted
CRM_EX_FAILED_PROMOTED = 9, //!< Service failed and possibly promoted
// sysexits.h
CRM_EX_USAGE = 64, //!< Command line usage error
CRM_EX_DATAERR = 65, //!< User-supplied data incorrect
CRM_EX_NOINPUT = 66, //!< Input file not available
CRM_EX_NOUSER = 67, //!< User does not exist
CRM_EX_NOHOST = 68, //!< Host unknown
CRM_EX_UNAVAILABLE = 69, //!< Needed service unavailable
CRM_EX_SOFTWARE = 70, //!< Internal software bug
CRM_EX_OSERR = 71, //!< External (OS/environmental) problem
CRM_EX_OSFILE = 72, //!< System file not usable
CRM_EX_CANTCREAT = 73, //!< File couldn't be created
CRM_EX_IOERR = 74, //!< File I/O error
CRM_EX_TEMPFAIL = 75, //!< Try again
CRM_EX_PROTOCOL = 76, //!< Protocol violated
CRM_EX_NOPERM = 77, //!< Non-file permission issue
CRM_EX_CONFIG = 78, //!< Misconfiguration
// Custom
CRM_EX_FATAL = 100, //!< Do not respawn
CRM_EX_PANIC = 101, //!< Panic the local host
CRM_EX_DISCONNECT = 102, //!< Lost connection to something
CRM_EX_OLD = 103, //!< Update older than existing config
CRM_EX_DIGEST = 104, //!< Digest comparison failed
CRM_EX_NOSUCH = 105, //!< Requested item does not exist
CRM_EX_QUORUM = 106, //!< Local partition does not have quorum
CRM_EX_UNSAFE = 107, //!< Requires --force or new conditions
CRM_EX_EXISTS = 108, //!< Requested item already exists
CRM_EX_MULTIPLE = 109, //!< Requested item has multiple matches
CRM_EX_EXPIRED = 110, //!< Requested item has expired
CRM_EX_NOT_YET_IN_EFFECT = 111, //!< Requested item is not in effect
CRM_EX_INDETERMINATE = 112, //!< Could not determine status
CRM_EX_UNSATISFIED = 113, //!< Requested item does not satisfy constraints
CRM_EX_NO_DC = 114, //!< DC is not yet elected, e.g. right after cluster restart
// Other
CRM_EX_TIMEOUT = 124, //!< Convention from timeout(1)
/* Anything above 128 overlaps with some shells' use of these values for
* "interrupted by signal N", and so may be unreliable when detected by
* shell scripts.
*/
// OCF Resource Agent API 1.1
CRM_EX_DEGRADED = 190, //!< Service active but more likely to fail soon
CRM_EX_DEGRADED_PROMOTED = 191, //!< Service promoted but more likely to fail soon
/* Custom
*
* This can be used to initialize exit status variables or to indicate that
* a command is pending (which is what the controller uses it for).
*/
CRM_EX_NONE = 193, //!< No exit status available
CRM_EX_MAX = 255, //!< Ensure crm_exit_t can hold this
} crm_exit_t;
/*!
* \brief Execution status
*
* These codes are used to specify the result of the attempt to execute an
* agent, rather than the agent's result itself.
*/
enum pcmk_exec_status {
PCMK_EXEC_UNKNOWN = -2, //!< Used only to initialize variables
PCMK_EXEC_PENDING = -1, //!< Action is in progress
PCMK_EXEC_DONE, //!< Action completed, result is known
PCMK_EXEC_CANCELLED, //!< Action was cancelled
PCMK_EXEC_TIMEOUT, //!< Action did not complete in time
PCMK_EXEC_NOT_SUPPORTED, //!< Agent does not implement requested action
PCMK_EXEC_ERROR, //!< Execution failed, may be retried
PCMK_EXEC_ERROR_HARD, //!< Execution failed, do not retry on node
PCMK_EXEC_ERROR_FATAL, //!< Execution failed, do not retry anywhere
PCMK_EXEC_NOT_INSTALLED, //!< Agent or dependency not available locally
PCMK_EXEC_NOT_CONNECTED, //!< No connection to executor
PCMK_EXEC_INVALID, //!< Action cannot be attempted (e.g. shutdown)
PCMK_EXEC_NO_FENCE_DEVICE, //!< No fence device is configured for target
PCMK_EXEC_NO_SECRETS, //!< Necessary CIB secrets are unavailable
// Add new values above here then update this one below
PCMK_EXEC_MAX = PCMK_EXEC_NO_SECRETS, //!< Maximum value for this enum
};
/*!
* \brief Types of Pacemaker result codes
*
* A particular integer can have different meanings within different Pacemaker
* result code families. It may be interpretable within zero, one, or multiple
* families.
*
* These values are useful for specifying how an integer result code should be
* interpreted in situations involving a generic integer value. For example, a
* function that can process multiple types of result codes might accept an
* arbitrary integer argument along with a \p pcmk_result_type argument that
* specifies how to interpret the integer.
*/
enum pcmk_result_type {
pcmk_result_legacy = 0, //!< Legacy API function return code
pcmk_result_rc = 1, //!< Standard Pacemaker return code
pcmk_result_exitcode = 2, //!< Exit status code
pcmk_result_exec_status = 3, //!< Execution status
};
int pcmk_result_get_strings(int code, enum pcmk_result_type type,
const char **name, const char **desc);
const char *pcmk_rc_name(int rc);
// NOTE: sbd (as of at least 1.5.2) uses this
const char *pcmk_rc_str(int rc);
crm_exit_t pcmk_rc2exitc(int rc);
enum ocf_exitcode pcmk_rc2ocf(int rc);
int pcmk_rc2legacy(int rc);
int pcmk_legacy2rc(int legacy_rc);
// NOTE: sbd (as of at least 1.5.2) uses this
const char *pcmk_strerror(int rc);
const char *pcmk_errorname(int rc);
const char *crm_exit_name(crm_exit_t exit_code);
// NOTE: sbd (as of at least 1.5.2) uses this
const char *crm_exit_str(crm_exit_t exit_code);
_Noreturn crm_exit_t crm_exit(crm_exit_t rc);
/* coverity[+kill] */
void crm_abort(const char *file, const char *function, int line,
const char *condition, gboolean do_core, gboolean do_fork);
static inline const char *
pcmk_exec_status_str(enum pcmk_exec_status status)
{
switch (status) {
case PCMK_EXEC_PENDING: return "Pending";
case PCMK_EXEC_DONE: return "Done";
case PCMK_EXEC_CANCELLED: return "Cancelled";
case PCMK_EXEC_TIMEOUT: return "Timed out";
case PCMK_EXEC_NOT_SUPPORTED: return "Unsupported";
case PCMK_EXEC_ERROR: return "Error";
case PCMK_EXEC_ERROR_HARD: return "Hard error";
case PCMK_EXEC_ERROR_FATAL: return "Fatal error";
case PCMK_EXEC_NOT_INSTALLED: return "Not installed";
case PCMK_EXEC_NOT_CONNECTED: return "Internal communication failure";
case PCMK_EXEC_INVALID: return "Cannot execute now";
case PCMK_EXEC_NO_FENCE_DEVICE: return "No fence device";
case PCMK_EXEC_NO_SECRETS: return "CIB secrets unavailable";
default: return "Unrecognized status (bug?)";
}
}
#ifdef __cplusplus
}
#endif
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
#include
#endif
#endif
diff --git a/lib/common/results.c b/lib/common/results.c
index abbc014665..29974a1b03 100644
--- a/lib/common/results.c
+++ b/lib/common/results.c
@@ -1,1315 +1,1322 @@
/*
* Copyright 2004-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
G_DEFINE_QUARK(pcmk-rc-error-quark, pcmk__rc_error)
G_DEFINE_QUARK(pcmk-exitc-error-quark, pcmk__exitc_error)
// General (all result code types)
/*!
* \brief Get the name and description of a given result code
*
* A result code can be interpreted as a member of any one of several families.
*
* \param[in] code The result code to look up
* \param[in] type How \p code should be interpreted
* \param[out] name Where to store the result code's name
* \param[out] desc Where to store the result code's description
*
* \return Standard Pacemaker return code
*/
int
pcmk_result_get_strings(int code, enum pcmk_result_type type, const char **name,
const char **desc)
{
const char *code_name = NULL;
const char *code_desc = NULL;
switch (type) {
case pcmk_result_legacy:
code_name = pcmk_errorname(code);
code_desc = pcmk_strerror(code);
break;
case pcmk_result_rc:
code_name = pcmk_rc_name(code);
code_desc = pcmk_rc_str(code);
break;
case pcmk_result_exitcode:
code_name = crm_exit_name(code);
code_desc = crm_exit_str((crm_exit_t) code);
break;
default:
return pcmk_rc_undetermined;
}
if (name != NULL) {
*name = code_name;
}
if (desc != NULL) {
*desc = code_desc;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Get the lower and upper bounds of a result code family
*
* \param[in] type Type of result code
* \param[out] lower Where to store the lower bound
* \param[out] upper Where to store the upper bound
*
* \return Standard Pacemaker return code
*
* \note There is no true upper bound on standard Pacemaker return codes or
* legacy return codes. All system \p errno values are valid members of
* these result code families, and there is no global upper limit nor a
* constant by which to refer to the highest \p errno value on a given
* system.
*/
int
pcmk__result_bounds(enum pcmk_result_type type, int *lower, int *upper)
{
pcmk__assert((lower != NULL) && (upper != NULL));
switch (type) {
case pcmk_result_legacy:
*lower = pcmk_ok;
*upper = 256; // should be enough for almost any system error code
break;
case pcmk_result_rc:
*lower = pcmk_rc_error - pcmk__n_rc + 1;
*upper = 256;
break;
case pcmk_result_exitcode:
*lower = CRM_EX_OK;
*upper = CRM_EX_MAX;
break;
default:
*lower = 0;
*upper = -1;
return pcmk_rc_undetermined;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Log a failed assertion
*
* \param[in] file File making the assertion
* \param[in] function Function making the assertion
* \param[in] line Line of file making the assertion
* \param[in] assert_condition String representation of assertion
*/
static void
log_assertion_as(const char *file, const char *function, int line,
const char *assert_condition)
{
if (!pcmk__is_daemon) {
crm_enable_stderr(TRUE); // Make sure command-line user sees message
}
crm_err("%s: Triggered fatal assertion at %s:%d : %s",
function, file, line, assert_condition);
}
/* coverity[+kill] */
/*!
* \internal
* \brief Log a failed assertion and abort
*
* \param[in] file File making the assertion
* \param[in] function Function making the assertion
* \param[in] line Line of file making the assertion
* \param[in] assert_condition String representation of assertion
*
* \note This does not return
*/
_Noreturn void
pcmk__abort_as(const char *file, const char *function, int line,
const char *assert_condition)
{
log_assertion_as(file, function, line, assert_condition);
abort();
}
/* coverity[+kill] */
/*!
* \internal
* \brief Handle a failed assertion
*
* When called by a daemon, fork a child that aborts (to dump core), otherwise
* abort the current process.
*
* \param[in] file File making the assertion
* \param[in] function Function making the assertion
* \param[in] line Line of file making the assertion
* \param[in] assert_condition String representation of assertion
*/
static void
fail_assert_as(const char *file, const char *function, int line,
const char *assert_condition)
{
int status = 0;
pid_t pid = 0;
if (!pcmk__is_daemon) {
pcmk__abort_as(file, function, line, assert_condition); // No return
}
pid = fork();
switch (pid) {
case -1: // Fork failed
crm_warn("%s: Cannot dump core for non-fatal assertion at %s:%d "
": %s", function, file, line, assert_condition);
break;
case 0: // Child process: just abort to dump core
abort();
break;
default: // Parent process: wait for child
crm_err("%s: Forked child [%d] to record non-fatal assertion at "
"%s:%d : %s", function, pid, file, line, assert_condition);
crm_write_blackbox(SIGTRAP, NULL);
do {
if (waitpid(pid, &status, 0) == pid) {
return; // Child finished dumping core
}
} while (errno == EINTR);
if (errno == ECHILD) {
// crm_mon ignores SIGCHLD
crm_trace("Cannot wait on forked child [%d] "
"(SIGCHLD is probably ignored)", pid);
} else {
crm_err("Cannot wait on forked child [%d]: %s",
pid, pcmk_rc_str(errno));
}
break;
}
}
/* coverity[+kill] */
void
crm_abort(const char *file, const char *function, int line,
const char *assert_condition, gboolean do_core, gboolean do_fork)
{
if (!do_fork) {
pcmk__abort_as(file, function, line, assert_condition); // No return
} else if (do_core) {
fail_assert_as(file, function, line, assert_condition);
} else {
log_assertion_as(file, function, line, assert_condition);
}
}
// @COMPAT Legacy function return codes
//! \deprecated Use standard return codes and pcmk_rc_name() instead
const char *
pcmk_errorname(int rc)
{
rc = abs(rc);
switch (rc) {
case pcmk_err_generic: return "pcmk_err_generic";
case pcmk_err_no_quorum: return "pcmk_err_no_quorum";
case pcmk_err_schema_validation: return "pcmk_err_schema_validation";
case pcmk_err_transform_failed: return "pcmk_err_transform_failed";
case pcmk_err_old_data: return "pcmk_err_old_data";
case pcmk_err_diff_failed: return "pcmk_err_diff_failed";
case pcmk_err_diff_resync: return "pcmk_err_diff_resync";
case pcmk_err_cib_modified: return "pcmk_err_cib_modified";
case pcmk_err_cib_backup: return "pcmk_err_cib_backup";
case pcmk_err_cib_save: return "pcmk_err_cib_save";
case pcmk_err_cib_corrupt: return "pcmk_err_cib_corrupt";
case pcmk_err_multiple: return "pcmk_err_multiple";
case pcmk_err_node_unknown: return "pcmk_err_node_unknown";
case pcmk_err_already: return "pcmk_err_already";
case pcmk_err_bad_nvpair: return "pcmk_err_bad_nvpair";
case pcmk_err_unknown_format: return "pcmk_err_unknown_format";
default: return pcmk_rc_name(rc); // system errno
}
}
//! \deprecated Use standard return codes and pcmk_rc_str() instead
const char *
pcmk_strerror(int rc)
{
return pcmk_rc_str(pcmk_legacy2rc(rc));
}
// Standard Pacemaker API return codes
/* This array is used only for nonzero values of pcmk_rc_e. Its values must be
* kept in the exact reverse order of the enum value numbering (i.e. add new
* values to the end of the array).
*/
static const struct pcmk__rc_info {
const char *name;
const char *desc;
int legacy_rc;
} pcmk__rcs[] = {
{ "pcmk_rc_error",
"Error",
-pcmk_err_generic,
},
{ "pcmk_rc_unknown_format",
"Unknown output format",
-pcmk_err_unknown_format,
},
{ "pcmk_rc_bad_nvpair",
"Bad name/value pair given",
-pcmk_err_bad_nvpair,
},
{ "pcmk_rc_already",
"Already in requested state",
-pcmk_err_already,
},
{ "pcmk_rc_node_unknown",
"Node not found",
-pcmk_err_node_unknown,
},
{ "pcmk_rc_multiple",
"Resource active on multiple nodes",
-pcmk_err_multiple,
},
{ "pcmk_rc_cib_corrupt",
"Could not parse on-disk configuration",
-pcmk_err_cib_corrupt,
},
{ "pcmk_rc_cib_save",
"Could not save new configuration to disk",
-pcmk_err_cib_save,
},
{ "pcmk_rc_cib_backup",
"Could not archive previous configuration",
-pcmk_err_cib_backup,
},
{ "pcmk_rc_cib_modified",
"On-disk configuration was manually modified",
-pcmk_err_cib_modified,
},
{ "pcmk_rc_diff_resync",
"Application of update diff failed, requesting full refresh",
-pcmk_err_diff_resync,
},
{ "pcmk_rc_diff_failed",
"Application of update diff failed",
-pcmk_err_diff_failed,
},
{ "pcmk_rc_old_data",
"Update was older than existing configuration",
-pcmk_err_old_data,
},
{ "pcmk_rc_transform_failed",
"Schema transform failed",
-pcmk_err_transform_failed,
},
{ "pcmk_rc_schema_unchanged",
"Schema is already the latest available",
-pcmk_err_schema_unchanged,
},
{ "pcmk_rc_schema_validation",
"Update does not conform to the configured schema",
-pcmk_err_schema_validation,
},
{ "pcmk_rc_no_quorum",
"Operation requires quorum",
-pcmk_err_no_quorum,
},
{ "pcmk_rc_ipc_unauthorized",
"IPC server is blocked by unauthorized process",
-pcmk_err_generic,
},
{ "pcmk_rc_ipc_unresponsive",
"IPC server is unresponsive",
-pcmk_err_generic,
},
{ "pcmk_rc_ipc_pid_only",
"IPC server process is active but not accepting connections",
-pcmk_err_generic,
},
{ "pcmk_rc_op_unsatisfied",
"Not applicable under current conditions",
-pcmk_err_generic,
},
{ "pcmk_rc_undetermined",
"Result undetermined",
-pcmk_err_generic,
},
{ "pcmk_rc_before_range",
"Result occurs before given range",
-pcmk_err_generic,
},
{ "pcmk_rc_within_range",
"Result occurs within given range",
-pcmk_err_generic,
},
{ "pcmk_rc_after_range",
"Result occurs after given range",
-pcmk_err_generic,
},
{ "pcmk_rc_no_output",
"Output message produced no output",
-pcmk_err_generic,
},
{ "pcmk_rc_no_input",
"Input file not available",
-pcmk_err_generic,
},
{ "pcmk_rc_underflow",
"Value too small to be stored in data type",
-pcmk_err_generic,
},
{ "pcmk_rc_dot_error",
"Error writing dot(1) file",
-pcmk_err_generic,
},
{ "pcmk_rc_graph_error",
"Error writing graph file",
-pcmk_err_generic,
},
{ "pcmk_rc_invalid_transition",
"Cluster simulation produced invalid transition",
-pcmk_err_generic,
},
{ "pcmk_rc_unpack_error",
"Unable to parse CIB XML",
-pcmk_err_generic,
},
{ "pcmk_rc_duplicate_id",
"Two or more XML elements have the same ID",
-pcmk_err_generic,
},
{ "pcmk_rc_disabled",
"Disabled",
-pcmk_err_generic,
},
{ "pcmk_rc_bad_input",
"Bad input value provided",
-pcmk_err_generic,
},
{ "pcmk_rc_bad_xml_patch",
"Bad XML patch format",
-pcmk_err_generic,
},
{ "pcmk_rc_no_transaction",
"No active transaction found",
-pcmk_err_generic,
},
{ "pcmk_rc_ns_resolution",
"Nameserver resolution error",
-pcmk_err_generic,
},
{ "pcmk_rc_compression",
"Compression/decompression error",
-pcmk_err_generic,
},
{ "pcmk_rc_no_dc",
"DC is not yet elected",
-pcmk_err_generic,
},
{ "pcmk_rc_ipc_more",
"More IPC message fragments to send",
-pcmk_err_generic,
},
{ "pcmk_rc_cs_internal",
"Internal corosync error",
-pcmk_err_generic,
},
+ { "pcmk_rc_digest_mismatch",
+ "Digest does not match expected value",
+ -pcmk_err_generic,
+ },
};
/*!
* \internal
* \brief The number of enum pcmk_rc_e values, excluding \c pcmk_rc_ok
*
* This constant stores the number of negative standard Pacemaker return codes.
* These represent Pacemaker-custom error codes. The count does not include
* positive system error numbers, nor does it include \c pcmk_rc_ok (success).
*/
const size_t pcmk__n_rc = PCMK__NELEM(pcmk__rcs);
/*!
* \brief Get a return code constant name as a string
*
* \param[in] rc Integer return code to convert
*
* \return String of constant name corresponding to rc
*/
const char *
pcmk_rc_name(int rc)
{
if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < pcmk__n_rc)) {
return pcmk__rcs[pcmk_rc_error - rc].name;
}
switch (rc) {
case pcmk_rc_ok: return "pcmk_rc_ok";
case E2BIG: return "E2BIG";
case EACCES: return "EACCES";
case EADDRINUSE: return "EADDRINUSE";
case EADDRNOTAVAIL: return "EADDRNOTAVAIL";
case EAFNOSUPPORT: return "EAFNOSUPPORT";
case EAGAIN: return "EAGAIN";
case EALREADY: return "EALREADY";
case EBADF: return "EBADF";
case EBADMSG: return "EBADMSG";
case EBUSY: return "EBUSY";
case ECANCELED: return "ECANCELED";
case ECHILD: return "ECHILD";
case ECOMM: return "ECOMM";
case ECONNABORTED: return "ECONNABORTED";
case ECONNREFUSED: return "ECONNREFUSED";
case ECONNRESET: return "ECONNRESET";
/* case EDEADLK: return "EDEADLK"; */
case EDESTADDRREQ: return "EDESTADDRREQ";
case EDOM: return "EDOM";
case EDQUOT: return "EDQUOT";
case EEXIST: return "EEXIST";
case EFAULT: return "EFAULT";
case EFBIG: return "EFBIG";
case EHOSTDOWN: return "EHOSTDOWN";
case EHOSTUNREACH: return "EHOSTUNREACH";
case EIDRM: return "EIDRM";
case EILSEQ: return "EILSEQ";
case EINPROGRESS: return "EINPROGRESS";
case EINTR: return "EINTR";
case EINVAL: return "EINVAL";
case EIO: return "EIO";
case EISCONN: return "EISCONN";
case EISDIR: return "EISDIR";
case ELIBACC: return "ELIBACC";
case ELOOP: return "ELOOP";
case EMFILE: return "EMFILE";
case EMLINK: return "EMLINK";
case EMSGSIZE: return "EMSGSIZE";
#ifdef EMULTIHOP // Not available on OpenBSD
case EMULTIHOP: return "EMULTIHOP";
#endif
case ENAMETOOLONG: return "ENAMETOOLONG";
case ENETDOWN: return "ENETDOWN";
case ENETRESET: return "ENETRESET";
case ENETUNREACH: return "ENETUNREACH";
case ENFILE: return "ENFILE";
case ENOBUFS: return "ENOBUFS";
case ENODATA: return "ENODATA";
case ENODEV: return "ENODEV";
case ENOENT: return "ENOENT";
case ENOEXEC: return "ENOEXEC";
case ENOKEY: return "ENOKEY";
case ENOLCK: return "ENOLCK";
#ifdef ENOLINK // Not available on OpenBSD
case ENOLINK: return "ENOLINK";
#endif
case ENOMEM: return "ENOMEM";
case ENOMSG: return "ENOMSG";
case ENOPROTOOPT: return "ENOPROTOOPT";
case ENOSPC: return "ENOSPC";
#ifdef ENOSR
case ENOSR: return "ENOSR";
#endif
#ifdef ENOSTR
case ENOSTR: return "ENOSTR";
#endif
case ENOSYS: return "ENOSYS";
case ENOTBLK: return "ENOTBLK";
case ENOTCONN: return "ENOTCONN";
case ENOTDIR: return "ENOTDIR";
case ENOTEMPTY: return "ENOTEMPTY";
case ENOTSOCK: return "ENOTSOCK";
#if ENOTSUP != EOPNOTSUPP
case ENOTSUP: return "ENOTSUP";
#endif
case ENOTTY: return "ENOTTY";
case ENOTUNIQ: return "ENOTUNIQ";
case ENXIO: return "ENXIO";
case EOPNOTSUPP: return "EOPNOTSUPP";
case EOVERFLOW: return "EOVERFLOW";
case EPERM: return "EPERM";
case EPFNOSUPPORT: return "EPFNOSUPPORT";
case EPIPE: return "EPIPE";
case EPROTO: return "EPROTO";
case EPROTONOSUPPORT: return "EPROTONOSUPPORT";
case EPROTOTYPE: return "EPROTOTYPE";
case ERANGE: return "ERANGE";
case EREMOTE: return "EREMOTE";
case EREMOTEIO: return "EREMOTEIO";
case EROFS: return "EROFS";
case ESHUTDOWN: return "ESHUTDOWN";
case ESPIPE: return "ESPIPE";
case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT";
case ESRCH: return "ESRCH";
case ESTALE: return "ESTALE";
case ETIME: return "ETIME";
case ETIMEDOUT: return "ETIMEDOUT";
case ETXTBSY: return "ETXTBSY";
#ifdef EUNATCH
case EUNATCH: return "EUNATCH";
#endif
case EUSERS: return "EUSERS";
/* case EWOULDBLOCK: return "EWOULDBLOCK"; */
case EXDEV: return "EXDEV";
#ifdef EBADE // Not available on OS X
case EBADE: return "EBADE";
case EBADFD: return "EBADFD";
case EBADSLT: return "EBADSLT";
case EDEADLOCK: return "EDEADLOCK";
case EBADR: return "EBADR";
case EBADRQC: return "EBADRQC";
case ECHRNG: return "ECHRNG";
#ifdef EISNAM // Not available on OS X, Illumos, Solaris
case EISNAM: return "EISNAM";
case EKEYEXPIRED: return "EKEYEXPIRED";
case EKEYREVOKED: return "EKEYREVOKED";
#endif
case EKEYREJECTED: return "EKEYREJECTED";
case EL2HLT: return "EL2HLT";
case EL2NSYNC: return "EL2NSYNC";
case EL3HLT: return "EL3HLT";
case EL3RST: return "EL3RST";
case ELIBBAD: return "ELIBBAD";
case ELIBMAX: return "ELIBMAX";
case ELIBSCN: return "ELIBSCN";
case ELIBEXEC: return "ELIBEXEC";
#ifdef ENOMEDIUM // Not available on OS X, Illumos, Solaris
case ENOMEDIUM: return "ENOMEDIUM";
case EMEDIUMTYPE: return "EMEDIUMTYPE";
#endif
case ENONET: return "ENONET";
case ENOPKG: return "ENOPKG";
case EREMCHG: return "EREMCHG";
case ERESTART: return "ERESTART";
case ESTRPIPE: return "ESTRPIPE";
#ifdef EUCLEAN // Not available on OS X, Illumos, Solaris
case EUCLEAN: return "EUCLEAN";
#endif
case EXFULL: return "EXFULL";
#endif // EBADE
default: return "Unknown";
}
}
/*!
* \brief Get a user-friendly description of a return code
*
* \param[in] rc Integer return code to convert
*
* \return String description of rc
*/
const char *
pcmk_rc_str(int rc)
{
if (rc == pcmk_rc_ok) {
return "OK";
}
if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < pcmk__n_rc)) {
return pcmk__rcs[pcmk_rc_error - rc].desc;
}
if (rc < 0) {
return "Error";
}
// Handle values that could be defined by system or by portability.h
switch (rc) {
#ifdef PCMK__ENOTUNIQ
case ENOTUNIQ: return "Name not unique on network";
#endif
#ifdef PCMK__ECOMM
case ECOMM: return "Communication error on send";
#endif
#ifdef PCMK__ELIBACC
case ELIBACC: return "Can not access a needed shared library";
#endif
#ifdef PCMK__EREMOTEIO
case EREMOTEIO: return "Remote I/O error";
#endif
#ifdef PCMK__ENOKEY
case ENOKEY: return "Required key not available";
#endif
#ifdef PCMK__ENODATA
case ENODATA: return "No data available";
#endif
#ifdef PCMK__ETIME
case ETIME: return "Timer expired";
#endif
#ifdef PCMK__EKEYREJECTED
case EKEYREJECTED: return "Key was rejected by service";
#endif
default: return strerror(rc);
}
}
// This returns negative values for errors
//! \deprecated Use standard return codes instead
int
pcmk_rc2legacy(int rc)
{
if (rc >= 0) {
return -rc; // OK or system errno
}
if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < pcmk__n_rc)) {
return pcmk__rcs[pcmk_rc_error - rc].legacy_rc;
}
return -pcmk_err_generic;
}
//! \deprecated Use standard return codes instead
int
pcmk_legacy2rc(int legacy_rc)
{
legacy_rc = abs(legacy_rc);
switch (legacy_rc) {
case pcmk_err_no_quorum: return pcmk_rc_no_quorum;
case pcmk_err_schema_validation: return pcmk_rc_schema_validation;
case pcmk_err_schema_unchanged: return pcmk_rc_schema_unchanged;
case pcmk_err_transform_failed: return pcmk_rc_transform_failed;
case pcmk_err_old_data: return pcmk_rc_old_data;
case pcmk_err_diff_failed: return pcmk_rc_diff_failed;
case pcmk_err_diff_resync: return pcmk_rc_diff_resync;
case pcmk_err_cib_modified: return pcmk_rc_cib_modified;
case pcmk_err_cib_backup: return pcmk_rc_cib_backup;
case pcmk_err_cib_save: return pcmk_rc_cib_save;
case pcmk_err_cib_corrupt: return pcmk_rc_cib_corrupt;
case pcmk_err_multiple: return pcmk_rc_multiple;
case pcmk_err_node_unknown: return pcmk_rc_node_unknown;
case pcmk_err_already: return pcmk_rc_already;
case pcmk_err_bad_nvpair: return pcmk_rc_bad_nvpair;
case pcmk_err_unknown_format: return pcmk_rc_unknown_format;
case pcmk_err_generic: return pcmk_rc_error;
case pcmk_ok: return pcmk_rc_ok;
default: return legacy_rc; // system errno
}
}
// Exit status codes
const char *
crm_exit_name(crm_exit_t exit_code)
{
switch (exit_code) {
case CRM_EX_OK: return "CRM_EX_OK";
case CRM_EX_ERROR: return "CRM_EX_ERROR";
case CRM_EX_INVALID_PARAM: return "CRM_EX_INVALID_PARAM";
case CRM_EX_UNIMPLEMENT_FEATURE: return "CRM_EX_UNIMPLEMENT_FEATURE";
case CRM_EX_INSUFFICIENT_PRIV: return "CRM_EX_INSUFFICIENT_PRIV";
case CRM_EX_NOT_INSTALLED: return "CRM_EX_NOT_INSTALLED";
case CRM_EX_NOT_CONFIGURED: return "CRM_EX_NOT_CONFIGURED";
case CRM_EX_NOT_RUNNING: return "CRM_EX_NOT_RUNNING";
case CRM_EX_PROMOTED: return "CRM_EX_PROMOTED";
case CRM_EX_FAILED_PROMOTED: return "CRM_EX_FAILED_PROMOTED";
case CRM_EX_USAGE: return "CRM_EX_USAGE";
case CRM_EX_DATAERR: return "CRM_EX_DATAERR";
case CRM_EX_NOINPUT: return "CRM_EX_NOINPUT";
case CRM_EX_NOUSER: return "CRM_EX_NOUSER";
case CRM_EX_NOHOST: return "CRM_EX_NOHOST";
case CRM_EX_UNAVAILABLE: return "CRM_EX_UNAVAILABLE";
case CRM_EX_SOFTWARE: return "CRM_EX_SOFTWARE";
case CRM_EX_OSERR: return "CRM_EX_OSERR";
case CRM_EX_OSFILE: return "CRM_EX_OSFILE";
case CRM_EX_CANTCREAT: return "CRM_EX_CANTCREAT";
case CRM_EX_IOERR: return "CRM_EX_IOERR";
case CRM_EX_TEMPFAIL: return "CRM_EX_TEMPFAIL";
case CRM_EX_PROTOCOL: return "CRM_EX_PROTOCOL";
case CRM_EX_NOPERM: return "CRM_EX_NOPERM";
case CRM_EX_CONFIG: return "CRM_EX_CONFIG";
case CRM_EX_FATAL: return "CRM_EX_FATAL";
case CRM_EX_PANIC: return "CRM_EX_PANIC";
case CRM_EX_DISCONNECT: return "CRM_EX_DISCONNECT";
case CRM_EX_DIGEST: return "CRM_EX_DIGEST";
case CRM_EX_NOSUCH: return "CRM_EX_NOSUCH";
case CRM_EX_QUORUM: return "CRM_EX_QUORUM";
case CRM_EX_UNSAFE: return "CRM_EX_UNSAFE";
case CRM_EX_EXISTS: return "CRM_EX_EXISTS";
case CRM_EX_MULTIPLE: return "CRM_EX_MULTIPLE";
case CRM_EX_EXPIRED: return "CRM_EX_EXPIRED";
case CRM_EX_NOT_YET_IN_EFFECT: return "CRM_EX_NOT_YET_IN_EFFECT";
case CRM_EX_INDETERMINATE: return "CRM_EX_INDETERMINATE";
case CRM_EX_UNSATISFIED: return "CRM_EX_UNSATISFIED";
case CRM_EX_NO_DC: return "CRM_EX_NO_DC";
case CRM_EX_OLD: return "CRM_EX_OLD";
case CRM_EX_TIMEOUT: return "CRM_EX_TIMEOUT";
case CRM_EX_DEGRADED: return "CRM_EX_DEGRADED";
case CRM_EX_DEGRADED_PROMOTED: return "CRM_EX_DEGRADED_PROMOTED";
case CRM_EX_NONE: return "CRM_EX_NONE";
case CRM_EX_MAX: return "CRM_EX_UNKNOWN";
}
return "CRM_EX_UNKNOWN";
}
const char *
crm_exit_str(crm_exit_t exit_code)
{
switch (exit_code) {
case CRM_EX_OK: return "OK";
case CRM_EX_ERROR: return "Error occurred";
case CRM_EX_INVALID_PARAM: return "Invalid parameter";
case CRM_EX_UNIMPLEMENT_FEATURE: return "Unimplemented";
case CRM_EX_INSUFFICIENT_PRIV: return "Insufficient privileges";
case CRM_EX_NOT_INSTALLED: return "Not installed";
case CRM_EX_NOT_CONFIGURED: return "Not configured";
case CRM_EX_NOT_RUNNING: return "Not running";
case CRM_EX_PROMOTED: return "Promoted";
case CRM_EX_FAILED_PROMOTED: return "Failed in promoted role";
case CRM_EX_USAGE: return "Incorrect usage";
case CRM_EX_DATAERR: return "Invalid data given";
case CRM_EX_NOINPUT: return "Input file not available";
case CRM_EX_NOUSER: return "User does not exist";
case CRM_EX_NOHOST: return "Host does not exist";
case CRM_EX_UNAVAILABLE: return "Necessary service unavailable";
case CRM_EX_SOFTWARE: return "Internal software bug";
case CRM_EX_OSERR: return "Operating system error occurred";
case CRM_EX_OSFILE: return "System file not available";
case CRM_EX_CANTCREAT: return "Cannot create output file";
case CRM_EX_IOERR: return "I/O error occurred";
case CRM_EX_TEMPFAIL: return "Temporary failure, try again";
case CRM_EX_PROTOCOL: return "Protocol violated";
case CRM_EX_NOPERM: return "Insufficient privileges";
case CRM_EX_CONFIG: return "Invalid configuration";
case CRM_EX_FATAL: return "Fatal error occurred, will not respawn";
case CRM_EX_PANIC: return "System panic required";
case CRM_EX_DISCONNECT: return "Not connected";
case CRM_EX_DIGEST: return "Digest mismatch";
case CRM_EX_NOSUCH: return "No such object";
case CRM_EX_QUORUM: return "Quorum required";
case CRM_EX_UNSAFE: return "Operation not safe";
case CRM_EX_EXISTS: return "Requested item already exists";
case CRM_EX_MULTIPLE: return "Multiple items match request";
case CRM_EX_EXPIRED: return "Requested item has expired";
case CRM_EX_NOT_YET_IN_EFFECT: return "Requested item is not yet in effect";
case CRM_EX_INDETERMINATE: return "Could not determine status";
case CRM_EX_UNSATISFIED: return "Not applicable under current conditions";
case CRM_EX_NO_DC: return "DC is not yet elected";
case CRM_EX_OLD: return "Update was older than existing configuration";
case CRM_EX_TIMEOUT: return "Timeout occurred";
case CRM_EX_DEGRADED: return "Service is active but might fail soon";
case CRM_EX_DEGRADED_PROMOTED: return "Service is promoted but might fail soon";
case CRM_EX_NONE: return "No exit status available";
case CRM_EX_MAX: return "Error occurred";
}
if ((exit_code > 128) && (exit_code < CRM_EX_MAX)) {
return "Interrupted by signal";
}
return "Unknown exit status";
}
/*!
* \brief Map a function return code to the most similar exit code
*
* \param[in] rc Function return code
*
* \return Most similar exit code
*/
crm_exit_t
pcmk_rc2exitc(int rc)
{
switch (rc) {
case pcmk_rc_ok:
case pcmk_rc_no_output: // quiet mode, or nothing to output
return CRM_EX_OK;
case pcmk_rc_no_quorum:
return CRM_EX_QUORUM;
case pcmk_rc_old_data:
return CRM_EX_OLD;
case pcmk_rc_cib_corrupt:
case pcmk_rc_schema_validation:
case pcmk_rc_transform_failed:
case pcmk_rc_unpack_error:
return CRM_EX_CONFIG;
case pcmk_rc_bad_nvpair:
return CRM_EX_INVALID_PARAM;
case EACCES:
return CRM_EX_INSUFFICIENT_PRIV;
case EBADF:
case EINVAL:
case EFAULT:
case ENOSYS:
case EOVERFLOW:
case pcmk_rc_underflow:
case pcmk_rc_compression:
case pcmk_rc_cs_internal:
return CRM_EX_SOFTWARE;
case EBADMSG:
case EMSGSIZE:
case ENOMSG:
case ENOPROTOOPT:
case EPROTO:
case EPROTONOSUPPORT:
case EPROTOTYPE:
return CRM_EX_PROTOCOL;
case ECOMM:
case ENOMEM:
return CRM_EX_OSERR;
case ECONNABORTED:
case ECONNREFUSED:
case ECONNRESET:
case ENOTCONN:
return CRM_EX_DISCONNECT;
case EEXIST:
case pcmk_rc_already:
return CRM_EX_EXISTS;
case EIO:
case pcmk_rc_dot_error:
case pcmk_rc_graph_error:
return CRM_EX_IOERR;
case ENOTSUP:
#if EOPNOTSUPP != ENOTSUP
case EOPNOTSUPP:
#endif
return CRM_EX_UNIMPLEMENT_FEATURE;
case ENOTUNIQ:
case pcmk_rc_multiple:
return CRM_EX_MULTIPLE;
case ENODEV:
case ENOENT:
case ENXIO:
case pcmk_rc_no_transaction:
case pcmk_rc_unknown_format:
return CRM_EX_NOSUCH;
case pcmk_rc_node_unknown:
case pcmk_rc_ns_resolution:
return CRM_EX_NOHOST;
case ETIME:
case ETIMEDOUT:
return CRM_EX_TIMEOUT;
case EAGAIN:
case EBUSY:
return CRM_EX_UNSATISFIED;
case pcmk_rc_before_range:
return CRM_EX_NOT_YET_IN_EFFECT;
case pcmk_rc_after_range:
return CRM_EX_EXPIRED;
case pcmk_rc_undetermined:
return CRM_EX_INDETERMINATE;
case pcmk_rc_op_unsatisfied:
return CRM_EX_UNSATISFIED;
case pcmk_rc_within_range:
return CRM_EX_OK;
case pcmk_rc_no_input:
return CRM_EX_NOINPUT;
case pcmk_rc_duplicate_id:
return CRM_EX_MULTIPLE;
case pcmk_rc_bad_input:
case pcmk_rc_bad_xml_patch:
return CRM_EX_DATAERR;
case pcmk_rc_no_dc:
return CRM_EX_NO_DC;
+ case pcmk_rc_digest_mismatch:
+ return CRM_EX_DIGEST;
+
default:
return CRM_EX_ERROR;
}
}
/*!
* \brief Map a function return code to the most similar OCF exit code
*
* \param[in] rc Function return code
*
* \return Most similar OCF exit code
*/
enum ocf_exitcode
pcmk_rc2ocf(int rc)
{
switch (rc) {
case pcmk_rc_ok:
return PCMK_OCF_OK;
case pcmk_rc_bad_nvpair:
return PCMK_OCF_INVALID_PARAM;
case EACCES:
return PCMK_OCF_INSUFFICIENT_PRIV;
case ENOTSUP:
#if EOPNOTSUPP != ENOTSUP
case EOPNOTSUPP:
#endif
return PCMK_OCF_UNIMPLEMENT_FEATURE;
default:
return PCMK_OCF_UNKNOWN_ERROR;
}
}
// Other functions
/*!
* \brief Map a getaddrinfo() return code to the most similar Pacemaker
* return code
*
* \param[in] gai getaddrinfo() return code
*
* \return Most similar Pacemaker return code
*/
int
pcmk__gaierror2rc(int gai)
{
switch (gai) {
case 0:
return pcmk_rc_ok;
case EAI_AGAIN:
return EAGAIN;
case EAI_BADFLAGS:
case EAI_SERVICE:
return EINVAL;
case EAI_FAMILY:
return EAFNOSUPPORT;
case EAI_MEMORY:
return ENOMEM;
case EAI_NONAME:
return pcmk_rc_node_unknown;
case EAI_SOCKTYPE:
return ESOCKTNOSUPPORT;
case EAI_SYSTEM:
return errno;
default:
return pcmk_rc_ns_resolution;
}
}
/*!
* \brief Map a bz2 return code to the most similar Pacemaker return code
*
* \param[in] bz2 bz2 return code
*
* \return Most similar Pacemaker return code
*/
int
pcmk__bzlib2rc(int bz2)
{
switch (bz2) {
case BZ_OK:
case BZ_RUN_OK:
case BZ_FLUSH_OK:
case BZ_FINISH_OK:
case BZ_STREAM_END:
return pcmk_rc_ok;
case BZ_MEM_ERROR:
return ENOMEM;
case BZ_DATA_ERROR:
case BZ_DATA_ERROR_MAGIC:
case BZ_UNEXPECTED_EOF:
return pcmk_rc_bad_input;
case BZ_IO_ERROR:
return EIO;
case BZ_OUTBUFF_FULL:
return EFBIG;
default:
return pcmk_rc_compression;
}
}
/*!
* \internal
* \brief Map a corosync return code to the most similar Pacemaker return code
*
* \param[in] cs corosync return code
*
* \return Most similar Pacemaker return code
*/
int
pcmk__corosync2rc(int cs)
{
#if SUPPORT_COROSYNC
switch (cs) {
case CS_OK:
return pcmk_rc_ok;
/* These are all-purpose internal error occurred codes. */
case CS_ERR_LIBRARY:
case CS_ERR_FAILED_OPERATION:
return pcmk_rc_cs_internal;
/* None of these are returned by corosync >= 2.0.0 so it's not worth
* defining standard Pacemaker return codes to match.
*/
case CS_ERR_VERSION:
case CS_ERR_BAD_FLAGS:
return pcmk_rc_error;
/* This is only returned by corosync when qb_trie_create fails, which
* only happens when malloc fails, therefore...
*/
case CS_ERR_INIT:
case CS_ERR_NO_MEMORY:
return ENOMEM;
case CS_ERR_TIMEOUT:
return ETIMEDOUT;
case CS_ERR_TRY_AGAIN:
return EAGAIN;
case CS_ERR_INVALID_PARAM:
return EINVAL;
case CS_ERR_BAD_HANDLE:
return EBADF;
case CS_ERR_BUSY:
return EBUSY;
case CS_ERR_ACCESS:
return EACCES;
case CS_ERR_NOT_EXIST:
case CS_ERR_NAME_NOT_FOUND:
return ENOENT;
case CS_ERR_NAME_TOO_LONG:
return ENAMETOOLONG;
case CS_ERR_EXIST:
return EEXIST;
case CS_ERR_NO_SPACE:
return ENOSPC;
case CS_ERR_INTERRUPT:
return EINTR;
case CS_ERR_NO_RESOURCES:
return EMFILE;
case CS_ERR_NOT_SUPPORTED:
return ENOTSUP;
case CS_ERR_BAD_OPERATION:
return EOPNOTSUPP;
case CS_ERR_MESSAGE_ERROR:
return EBADMSG;
/* These are still handled by corosync, but I can't find any evidence
* that they are returned anywhere. At one point, certain libqb error
* codes were converted into these errors but I don't see that happening
* anymore. Thus, just turn them into the default as well.
*/
case CS_ERR_QUEUE_FULL:
case CS_ERR_QUEUE_NOT_AVAILABLE:
return pcmk_rc_error;
case CS_ERR_TOO_BIG:
return EFBIG;
case CS_ERR_NO_SECTIONS:
return ENODATA;
}
#endif
return pcmk_rc_error;
}
crm_exit_t
crm_exit(crm_exit_t exit_status)
{
/* A compiler could theoretically use any type for crm_exit_t, but an int
* should always hold it, so cast to int to keep static analysis happy.
*/
if ((((int) exit_status) < 0) || (((int) exit_status) > CRM_EX_MAX)) {
exit_status = CRM_EX_ERROR;
}
crm_info("Exiting %s " QB_XS " with status %d (%s: %s)",
pcmk__s(crm_system_name, "process"), exit_status,
crm_exit_name(exit_status), crm_exit_str(exit_status));
pcmk_common_cleanup();
exit(exit_status);
}
/*
* External action results
*/
/*!
* \internal
* \brief Set the result of an action
*
* \param[out] result Where to set action result
* \param[in] exit_status OCF exit status to set
* \param[in] exec_status Execution status to set
* \param[in] exit_reason Human-friendly description of event to set
*/
void
pcmk__set_result(pcmk__action_result_t *result, int exit_status,
enum pcmk_exec_status exec_status, const char *exit_reason)
{
if (result == NULL) {
return;
}
result->exit_status = exit_status;
result->execution_status = exec_status;
if (!pcmk__str_eq(result->exit_reason, exit_reason, pcmk__str_none)) {
free(result->exit_reason);
result->exit_reason = (exit_reason == NULL)? NULL : strdup(exit_reason);
}
}
/*!
* \internal
* \brief Set the result of an action, with a formatted exit reason
*
* \param[out] result Where to set action result
* \param[in] exit_status OCF exit status to set
* \param[in] exec_status Execution status to set
* \param[in] format printf-style format for a human-friendly
* description of reason for result
* \param[in] ... arguments for \p format
*/
G_GNUC_PRINTF(4, 5)
void
pcmk__format_result(pcmk__action_result_t *result, int exit_status,
enum pcmk_exec_status exec_status,
const char *format, ...)
{
va_list ap;
int len = 0;
char *reason = NULL;
if (result == NULL) {
return;
}
result->exit_status = exit_status;
result->execution_status = exec_status;
if (format != NULL) {
va_start(ap, format);
len = vasprintf(&reason, format, ap);
pcmk__assert(len > 0);
va_end(ap);
}
free(result->exit_reason);
result->exit_reason = reason;
}
/*!
* \internal
* \brief Set the output of an action
*
* \param[out] result Action result to set output for
* \param[in] out Action output to set (must be dynamically
* allocated)
* \param[in] err Action error output to set (must be dynamically
* allocated)
*
* \note \p result will take ownership of \p out and \p err, so the caller
* should not free them.
*/
void
pcmk__set_result_output(pcmk__action_result_t *result, char *out, char *err)
{
if (result == NULL) {
return;
}
free(result->action_stdout);
result->action_stdout = out;
free(result->action_stderr);
result->action_stderr = err;
}
/*!
* \internal
* \brief Clear a result's exit reason, output, and error output
*
* \param[in,out] result Result to reset
*/
void
pcmk__reset_result(pcmk__action_result_t *result)
{
if (result == NULL) {
return;
}
free(result->exit_reason);
result->exit_reason = NULL;
free(result->action_stdout);
result->action_stdout = NULL;
free(result->action_stderr);
result->action_stderr = NULL;
}
/*!
* \internal
* \brief Copy the result of an action
*
* \param[in] src Result to copy
* \param[out] dst Where to copy \p src to
*/
void
pcmk__copy_result(const pcmk__action_result_t *src, pcmk__action_result_t *dst)
{
CRM_CHECK((src != NULL) && (dst != NULL), return);
dst->exit_status = src->exit_status;
dst->execution_status = src->execution_status;
dst->exit_reason = pcmk__str_copy(src->exit_reason);
dst->action_stdout = pcmk__str_copy(src->action_stdout);
dst->action_stderr = pcmk__str_copy(src->action_stderr);
}
diff --git a/tools/cibsecret.c b/tools/cibsecret.c
index 43479a8ab2..fce4108a39 100644
--- a/tools/cibsecret.c
+++ b/tools/cibsecret.c
@@ -1,1213 +1,1215 @@
/*
* Copyright 2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include // EINVAL, ENODEV, ENOENT, ENOTCONN
#include
#include
#include // setenv, unsetenv
-#include
#include // LOG_DEBUG
#include // umask, S_IRGRP, S_IROTH, ...
#include // WEXITSTATUS
#include // geteuid
#include
#include // xmlNode, xmlNodeGetContent
#include // xmlFree
#include // xmlChar
-#include // cib__signon_query
+#include // cib__clean_up_connection, cib__signon_query
#include
#include
#include // crm_strdup_printf
#include // crm_md5sum
#include // crm_element_value, PCMK_XA_*
#include // pcmk__query_node_name
#define SUMMARY "cibsecret - manage sensitive information in Pacemaker CIB"
#define LRM_MAGIC "lrm://"
#define SSH_OPTS "-o StrictHostKeyChecking=no"
static gchar **remainder = NULL;
static gboolean no_cib = FALSE;
static GOptionEntry entries[] = {
{ "no-cib", 'C', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &no_cib,
"Don't read or write the CIB",
NULL },
{ G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &remainder,
NULL,
NULL },
{ NULL }
};
/*!
* \internal
* \brief A function for running a command on remote hosts
*
* \param[in,out] out Output object
* \param[in] nodes A list of remote hosts
* \param[in] cmdline The command line to run
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note On error, \p out->err() will be called to record stderr of the process
*/
typedef int (*rsh_fn_t)(pcmk__output_t *out, gchar **nodes, const char *cmdline);
/*!
* \internal
* \brief A function for copying a file to remote hosts
*
* \param[in,out] out Output object
* \param[in] nodes A list of remote hosts
* \param[in] to The destination path on the remote host
* \param[in] from The local file (or directory) to copy
*
+ * \return Standard Pacemaker return code
+ *
* \note \p from can either be a single file or a directory. It cannot be
* be multiple files in a space-separated string. If multiple files need
* to be copied, either copy the entire directory at once or call this
* function multiple times.
+ *
+ * \note On error, \p out->err() will be called to record stderr of the process
*/
typedef int (*rcp_fn_t)(pcmk__output_t *out, gchar **nodes, const char *to,
const char *from);
struct subcommand_entry {
const char *name;
int args;
const char *usage;
bool requires_cib;
- /* The shell version of cibsecret exited with a wide variety of error codes
- * for all sorts of situations. Our standard Pacemaker return codes don't
- * really line up with what it was doing - either we don't have a code with
- * the right name, or we have one that doesn't map to the right exit code,
- * etc.
- *
- * For backwards compatibility, the subcommand handler functions will
- * return a standard Pacemaker so other functions here know what to do, but
- * it will also take exit_code as an out parameter for the subcommands to
- * set and for us to exit with.
- */
- int (*handler)(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
- crm_exit_t *exit_code);
+ int (*handler)(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn);
};
+/*!
+ * \internal
+ * \brief Run a command line process
+ *
+ * \param[in,out] out Output object
+ * \param[in] cmdline The command line to execute
+ * \param[out] standard_out If not NULL, where to save stdout of the process
+ *
+ * \return Standard Pacemaker return code
+ *
+ * \note On error, \p out->err() will be called to record stderr of the process
+ */
static int
run_cmdline(pcmk__output_t *out, const char *cmdline, char **standard_out)
{
int rc = pcmk_rc_ok;
gboolean success = FALSE;
GError *error = NULL;
gchar *sout = NULL;
gchar *serr = NULL;
gint status;
/* A failure here is a failure starting the program (for example, it doesn't
* exist on the $PATH), not that it ran but exited with an error code.
*/
success = g_spawn_command_line_sync(cmdline, &sout, &serr, &status, &error);
if (!success) {
out->err(out, "%s", error->message);
rc = pcmk_rc_error;
goto done;
}
/* A failure here indicates that the program exited with a non-zero exit
* code or due to a fatal signal.
*/
/* @FIXME @COMPAT g_spawn_check_exit_status is deprecated as of glib 2.70
* and is replaced with g_spawn_check_wait_status.
*/
success = g_spawn_check_exit_status(status, &error);
if (!success) {
out->err(out, "%s", error->message);
out->subprocess_output(out, WEXITSTATUS(status), sout, serr);
rc = pcmk_rc_error;
}
done:
pcmk__str_update(standard_out, sout);
g_free(sout);
g_free(serr);
g_clear_error(&error);
return rc;
}
static int
pssh(pcmk__output_t *out, gchar **nodes, const char *cmdline)
{
int rc = pcmk_rc_ok;
char *s = NULL;
gchar *hosts = g_strjoinv(" ", nodes);
s = crm_strdup_printf("pssh -i -H \"%s\" -x \"" SSH_OPTS "\" -- \"%s\"",
hosts, cmdline);
rc = run_cmdline(out, s, NULL);
free(s);
g_free(hosts);
return rc;
}
static int
pdsh(pcmk__output_t *out, gchar **nodes, const char *cmdline)
{
int rc = pcmk_rc_ok;
char *s = NULL;
gchar *hosts = g_strjoinv(",", nodes);
s = crm_strdup_printf("pdsh -w \"%s\" -- \"%s\"", hosts, cmdline);
setenv("PDSH_SSH_ARGS_APPEND", SSH_OPTS, 1);
rc = run_cmdline(out, s, NULL);
unsetenv("PDSH_SSH_ARGS_APPEND");
free(s);
g_free(hosts);
return rc;
}
static int
ssh(pcmk__output_t *out, gchar **nodes, const char *cmdline)
{
int rc = pcmk_rc_ok;
for (gchar **node = nodes; *node != NULL; node++) {
char *s = crm_strdup_printf("ssh " SSH_OPTS " \"%s\" -- \"%s\"",
*node, cmdline);
rc = run_cmdline(out, s, NULL);
free(s);
if (rc != pcmk_rc_ok) {
return rc;
}
}
return rc;
}
static int
pscp(pcmk__output_t *out, gchar **nodes, const char *to, const char *from)
{
int rc = pcmk_rc_ok;
char *s = NULL;
gchar *hosts = g_strjoinv(" ", nodes);
s = crm_strdup_printf("pscp.pssh -H \"%s\" -x \"-pr\" -x \"" SSH_OPTS "\" -- \"%s\" \"%s\"",
hosts, from, to);
rc = run_cmdline(out, s, NULL);
free(s);
g_free(hosts);
return rc;
}
static int
pdcp(pcmk__output_t *out, gchar **nodes, const char *to, const char *from)
{
int rc = pcmk_rc_ok;
char *s = NULL;
gchar *hosts = g_strjoinv(",", nodes);
s = crm_strdup_printf("pdcp -pr -w \"%s\" -- \"%s\" \"%s\"", hosts, from, to);
setenv("PDSH_SSH_ARGS_APPEND", SSH_OPTS, 1);
rc = run_cmdline(out, s, NULL);
unsetenv("PDSH_SSH_ARGS_APPEND");
free(s);
g_free(hosts);
return rc;
}
static int
scp(pcmk__output_t *out, gchar **nodes, const char *to, const char *from)
{
int rc = pcmk_rc_ok;
for (gchar **node = nodes; *node != NULL; node++) {
char *s = crm_strdup_printf("scp -pqr " SSH_OPTS " \"%s\" \"%s:%s\"",
from, *node, to);
rc = run_cmdline(out, s, NULL);
free(s);
if (rc != pcmk_rc_ok) {
return rc;
}
}
return rc;
}
static gchar **
reachable_hosts(pcmk__output_t *out, GList *all)
{
GPtrArray *reachable = NULL;
gchar *path = NULL;
path = g_find_program_in_path("fping");
reachable = g_ptr_array_new();
if ((path == NULL) || (geteuid() != 0)) {
for (GList *host = all; host != NULL; host = host->next) {
int rc = pcmk_rc_ok;
char *cmdline = crm_strdup_printf("ping -c 2 -q %s", (char *) host->data);
rc = run_cmdline(out, cmdline, NULL);
free(cmdline);
if (rc == pcmk_rc_ok) {
g_ptr_array_add(reachable, g_strdup(host->data));
}
}
} else {
GString *all_str = g_string_sized_new(64);
gchar **parts = NULL;
char *standard_out = NULL;
char *cmdline = NULL;
for (GList *host = all; host != NULL; host = host->next) {
pcmk__add_word(&all_str, 64, host->data);
}
cmdline = crm_strdup_printf("fping -a -q %s", all_str->str);
run_cmdline(out, cmdline, &standard_out);
parts = g_strsplit(standard_out, "\n", 0);
for (gchar **p = parts; *p != NULL; p++) {
if (pcmk__str_empty(*p)) {
continue;
}
g_ptr_array_add(reachable, g_strdup(*p));
}
free(cmdline);
free(standard_out);
g_string_free(all_str, TRUE);
g_strfreev(parts);
}
g_free(path);
g_ptr_array_add(reachable, NULL);
return (char **) g_ptr_array_free(reachable, FALSE);
}
struct node_data {
pcmk__output_t *out;
char *local_node;
const char *field;
GList *all_nodes;
};
static void
node_iter_helper(xmlNode *result, void *user_data)
{
struct node_data *data = user_data;
const char *uname = pcmk__xe_get(result, PCMK_XA_UNAME);
const char *id = pcmk__xe_get(result, data->field);
const char *name = pcmk__s(uname, id);
/* Filter out the local node */
if (pcmk__str_eq(name, data->local_node, pcmk__str_null_matches)) {
return;
}
data->all_nodes = g_list_append(data->all_nodes, g_strdup(name));
}
static gchar **
get_live_peers(pcmk__output_t *out)
{
int rc = pcmk_rc_ok;
xmlNode *xml_node = NULL;
gchar **reachable = NULL;
+ cib_t *cib = NULL;
struct node_data nd = {
.out = out,
+ .local_node = NULL,
.all_nodes = NULL
};
- /* Get the local node name. */
- rc = pcmk__query_node_name(out, 0, &(nd.local_node), 0);
+ rc = cib__signon_query(out, &cib, &xml_node);
if (rc != pcmk_rc_ok) {
- out->err(out, "Could not get local node name");
+ out->err(out, "Could not get list of cluster nodes");
goto done;
}
- /* Get a list of all node names, filtering out the local node. */
- rc = cib__signon_query(out, NULL, &xml_node);
- if (rc != pcmk_rc_ok) {
- out->err(out, "Could not get list of cluster nodes");
- goto done;
+ /* Get the local node name if possible. */
+ if (cib->variant != cib_file) {
+ rc = pcmk__query_node_name(out, 0, &(nd.local_node), 0);
+ if (rc != pcmk_rc_ok) {
+ out->err(out, "Could not get local node name");
+ goto done;
+ }
}
+ /* Filter out the local node from the list of all node names. If we don't
+ * have a local node (for instance, because CIB_file is set) then we'll
+ * just use the list of all node names instead.
+ */
nd.field = PCMK_XA_ID;
pcmk__xpath_foreach_result(xml_node->doc, PCMK__XP_MEMBER_NODE_CONFIG,
node_iter_helper, &nd);
nd.field = PCMK_XA_VALUE;
pcmk__xpath_foreach_result(xml_node->doc, PCMK__XP_GUEST_NODE_CONFIG,
node_iter_helper, &nd);
nd.field = PCMK_XA_ID;
pcmk__xpath_foreach_result(xml_node->doc, PCMK__XP_REMOTE_NODE_CONFIG,
node_iter_helper, &nd);
if (nd.all_nodes == NULL) {
goto done;
}
/* Get a list of all nodes that respond to pings */
reachable = reachable_hosts(out, nd.all_nodes);
/* Warn the user about any that didn't respond to pings */
for (const GList *iter = nd.all_nodes; iter != NULL; iter = iter->next) {
bool found = false;
for (gchar **host = reachable; *host != NULL; host++) {
if (pcmk__str_eq(iter->data, *host, pcmk__str_none)) {
found = true;
break;
}
}
if (!found) {
out->info(out, "Node %s is down - you'll need to update it "
"with `cibsecret sync` later", (char *) iter->data);
}
}
done:
+ cib__clean_up_connection(&cib);
+
free(nd.local_node);
free(xml_node);
if (nd.all_nodes != NULL) {
g_list_free_full(nd.all_nodes, g_free);
}
return reachable;
}
static int
sync_one_file(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
const char *path)
{
int rc = pcmk_rc_ok;
gchar *dirname = NULL;
gchar **peers = get_live_peers(out);
gchar *peer_str = NULL;
char *cmdline = NULL;
if (peers == NULL) {
return pcmk_rc_ok;
}
peer_str = g_strjoinv(" ", peers);
if (pcmk__str_eq(remainder[0], "delete", pcmk__str_none)) {
out->info(out, "Deleting %s from %s ...", path, peer_str);
} else {
out->info(out, "Syncing %s to %s ...", path, peer_str);
}
dirname = g_path_get_dirname(path);
cmdline = crm_strdup_printf("mkdir -p %s", dirname);
rc = rsh_fn(out, peers, cmdline);
if (rc != pcmk_rc_ok) {
goto done;
}
if (g_file_test(path, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_REGULAR)) {
char *sign_path = NULL;
rc = rcp_fn(out, peers, dirname, path);
if (rc != pcmk_rc_ok) {
goto done;
}
sign_path = crm_strdup_printf("%s.sign", path);
rc = rcp_fn(out, peers, dirname, sign_path);
free(sign_path);
} else {
free(cmdline);
cmdline = crm_strdup_printf("rm -f %s %s.sign", path, path);
rc = rsh_fn(out, peers, cmdline);
}
done:
free(cmdline);
g_free(dirname);
g_strfreev(peers);
g_free(peer_str);
return rc;
}
static int
check_cib_rsc(pcmk__output_t *out, const char *rsc)
{
int rc = pcmk_rc_ok;
char *cmdline = NULL;
if (no_cib) {
return rc;
}
cmdline = crm_strdup_printf("crm_resource -r %s -W", rsc);
rc = run_cmdline(out, cmdline, NULL);
free(cmdline);
return rc;
}
static bool
is_secret(const char *s)
{
if (no_cib) {
/* Assume that the secret is in the CIB if we can't connect */
return true;
}
return pcmk__str_eq(s, LRM_MAGIC, pcmk__str_none);
}
static char *
get_cib_param(pcmk__output_t *out, const char *rsc, const char *param)
{
int rc = pcmk_rc_ok;
char *cmdline = NULL;
char *standard_out = NULL;
char *retval = NULL;
char *xpath = NULL;
xmlNode *xml = NULL;
xmlNode *node = NULL;
xmlChar *content = NULL;
if (no_cib) {
return NULL;
}
cmdline = crm_strdup_printf("crm_resource -r %s -g %s --output-as=xml", rsc, param);
rc = run_cmdline(out, cmdline, &standard_out);
if (rc != pcmk_rc_ok) {
goto done;
}
xml = pcmk__xml_parse(standard_out);
if (xml == NULL) {
goto done;
}
xpath = crm_strdup_printf("//" PCMK_XE_ITEM "[@" PCMK_XA_NAME "='%s']", param);
node = pcmk__xpath_find_one(xml->doc, xpath, LOG_DEBUG);
if (node == NULL) {
goto done;
}
content = xmlNodeGetContent(node);
if (content != NULL) {
retval = pcmk__str_copy((char *) content);
xmlFree(content);
}
done:
free(cmdline);
free(standard_out);
free(xpath);
pcmk__xml_free(xml);
return retval;
}
static int
remove_cib_param(pcmk__output_t *out, const char *rsc, const char *param)
{
int rc = pcmk_rc_ok;
char *cmdline = NULL;
if (no_cib) {
return rc;
}
cmdline = crm_strdup_printf("crm_resource -r %s -d %s", rsc, param);
rc = run_cmdline(out, cmdline, NULL);
free(cmdline);
return rc;
}
static int
set_cib_param(pcmk__output_t *out, const char *rsc, const char *param,
const char *value)
{
int rc = pcmk_rc_ok;
char *cmdline = NULL;
if (no_cib) {
return rc;
}
cmdline = crm_strdup_printf("crm_resource -r %s -p %s -v %s", rsc, param,
value);
rc = run_cmdline(out, cmdline, NULL);
free(cmdline);
return rc;
}
static char *
local_files_get(const char *rsc, const char *param)
{
char *retval = NULL;
char *lf_file = NULL;
gchar *contents = NULL;
lf_file = crm_strdup_printf(PCMK__CIB_SECRETS_DIR "/%s/%s", rsc, param);
if (g_file_get_contents(lf_file, &contents, NULL, NULL)) {
contents = g_strchomp(contents);
retval = pcmk__str_copy(contents);
g_free(contents);
}
free(lf_file);
return retval;
}
static char *
local_files_getsum(const char *rsc, const char *param)
{
char *retval = NULL;
char *lf_file = NULL;
gchar *contents = NULL;
lf_file = crm_strdup_printf(PCMK__CIB_SECRETS_DIR "/%s/%s.sign", rsc, param);
if (g_file_get_contents(lf_file, &contents, NULL, NULL)) {
contents = g_strchomp(contents);
retval = pcmk__str_copy(contents);
g_free(contents);
}
free(lf_file);
return retval;
}
static int
local_files_remove(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
const char *rsc, const char *param)
{
int rc = pcmk_rc_ok;
char *lf_file = NULL;
char *cmdline = NULL;
lf_file = crm_strdup_printf(PCMK__CIB_SECRETS_DIR "/%s/%s", rsc, param);
cmdline = crm_strdup_printf("rm -f %s %s.sign", lf_file, lf_file);
rc = run_cmdline(out, cmdline, NULL);
- if (rc != pcmk_rc_ok) {
- goto done;
+ if (rc == pcmk_rc_ok) {
+ rc = sync_one_file(out, rsh_fn, rcp_fn, lf_file);
}
- rc = sync_one_file(out, rsh_fn, rcp_fn, lf_file);
-
-done:
free(lf_file);
free(cmdline);
return rc;
}
static int
local_files_set(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
const char *rsc, const char *param, const char *value)
{
char *contents = NULL;
char *lf_dir = NULL;
char *lf_file = NULL;
char *sign_file = NULL;
char *calc_sum = NULL;
int rc = pcmk_rc_ok;
lf_dir = crm_strdup_printf(PCMK__CIB_SECRETS_DIR "/%s", rsc);
if (g_mkdir_with_parents(lf_dir, 0700) != 0) {
rc = errno;
+ out->err(out, "Could not create directory %s: %s", lf_dir,
+ pcmk_rc_str(rc));
goto done;
}
lf_file = crm_strdup_printf("%s/%s", lf_dir, param);
contents = crm_strdup_printf("%s\n", value);
if (!g_file_set_contents(lf_file, contents, -1, NULL)) {
rc = EIO;
+ out->err(out, "Could not create file %s: %s", lf_file,
+ pcmk_rc_str(rc));
goto done;
}
free(contents);
sign_file = crm_strdup_printf("%s/%s.sign", lf_dir, param);
calc_sum = crm_md5sum(value);
contents = crm_strdup_printf("%s\n", calc_sum);
if (!g_file_set_contents(sign_file, contents, -1, NULL)) {
rc = EIO;
+ out->err(out, "Could not create file %s: %s", sign_file,
+ pcmk_rc_str(rc));
goto done;
}
rc = sync_one_file(out, rsh_fn, rcp_fn, lf_file);
done:
free(contents);
free(calc_sum);
free(sign_file);
free(lf_dir);
free(lf_file);
return rc;
}
static int
-subcommand_check(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
- crm_exit_t *exit_code)
+subcommand_check(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
{
int rc = pcmk_rc_ok;
const char *rsc = remainder[1];
const char *param = remainder[2];
char *value = NULL;
char *calc_sum = NULL;
char *local_sum = NULL;
char *local_value = NULL;
if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
- *exit_code = CRM_EX_NOSUCH;
rc = ENODEV;
goto done;
}
value = get_cib_param(out, rsc, param);
if ((value == NULL) || !is_secret(value)) {
out->err(out, "Resource %s parameter %s not set as secret, nothing to check",
rsc, param);
- *exit_code = CRM_EX_CONFIG;
- rc = EINVAL;
+
+ /* I don't like this error code, but (1) it maps to CRM_EX_CONFIG which
+ * is what the old cibsecret.in would return in this case, and (2) we
+ * return it all over the place for a variety of CIB checking errors.
+ */
+ rc = pcmk_rc_unpack_error;
goto done;
}
local_sum = local_files_getsum(rsc, param);
if (local_sum == NULL) {
out->err(out, "No checksum for resource %s parameter %s", rsc, param);
- *exit_code = CRM_EX_OSFILE;
rc = ENOENT;
goto done;
}
local_value = local_files_get(rsc, param);
if (local_value != NULL) {
calc_sum = crm_md5sum(local_value);
}
if ((local_value == NULL) || !pcmk__str_eq(calc_sum, local_sum, pcmk__str_none)) {
out->err(out, "Checksum mismatch for resource %s parameter %s", rsc, param);
- *exit_code = CRM_EX_DIGEST;
- rc = EINVAL;
+ rc = pcmk_rc_digest_mismatch;
}
done:
free(local_sum);
free(local_value);
free(calc_sum);
free(value);
return rc;
}
static int
-subcommand_delete(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
- crm_exit_t *exit_code)
+subcommand_delete(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
{
int rc = pcmk_rc_ok;
const char *rsc = remainder[1];
const char *param = remainder[2];
if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
- *exit_code = CRM_EX_NOSUCH;
- rc = ENODEV;
- goto done;
+ return ENODEV;
}
rc = local_files_remove(out, rsh_fn, rcp_fn, rsc, param);
if (rc != pcmk_rc_ok) {
- goto done;
+ return rc;
}
- rc = remove_cib_param(out, rsc, param);
-
-done:
- return rc;
+ return remove_cib_param(out, rsc, param);
}
static int
-subcommand_get(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
- crm_exit_t *exit_code)
+subcommand_get(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
{
- int rc = subcommand_check(out, rsh_fn, rcp_fn, exit_code);
+ int rc = subcommand_check(out, rsh_fn, rcp_fn);
char *value = NULL;
const char *rsc = remainder[1];
const char *param = remainder[2];
if (rc != pcmk_rc_ok) {
return rc;
}
value = local_files_get(rsc, param);
pcmk__assert(value != NULL);
out->info(out, "%s", value);
free(value);
return pcmk_rc_ok;
}
/* The previous shell implementation of cibsecret allowed passing the value
* to set (what would be remainder[3] here) via stdin, which we do not support
* here at the moment.
*/
static int
-subcommand_set(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
- crm_exit_t *exit_code)
+subcommand_set(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
{
int rc = pcmk_rc_ok;
const char *rsc = remainder[1];
const char *param = remainder[2];
const char *value = remainder[3];
char *current = NULL;
if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
- *exit_code = CRM_EX_NOSUCH;
rc = ENODEV;
goto done;
}
current = get_cib_param(out, rsc, param);
if ((current != NULL) && !pcmk__str_any_of(current, LRM_MAGIC, value, NULL)) {
out->err(out, "CIB value <%s> different for %s rsc parameter %s; please "
"delete it first", current, rsc, param);
- *exit_code = CRM_EX_CONFIG;
- rc = EINVAL;
+
+ /* I don't like this error code, but (1) it maps to CRM_EX_CONFIG which
+ * is what the old cibsecret.in would return in this case, and (2) we
+ * return it all over the place for a variety of CIB checking errors.
+ */
+ rc = pcmk_rc_unpack_error;
goto done;
}
rc = local_files_set(out, rsh_fn, rcp_fn, rsc, param, value);
if (rc != pcmk_rc_ok) {
goto done;
}
rc = set_cib_param(out, rsc, param, LRM_MAGIC);
done:
free(current);
return rc;
}
static int
-subcommand_stash(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
- crm_exit_t *exit_code)
+subcommand_stash(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
{
int rc = pcmk_rc_ok;
const char *rsc = remainder[1];
const char *param = remainder[2];
char *value = NULL;
if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
- *exit_code = CRM_EX_NOSUCH;
rc = ENODEV;
goto done;
}
value = get_cib_param(out, rsc, param);
if ((value == NULL) || is_secret(value)) {
if (value == NULL) {
out->err(out, "Nothing to stash for resource %s parameter %s", rsc,
param);
- *exit_code = CRM_EX_NOSUCH;
+ rc = ENOENT;
} else {
out->err(out, "Resource %s parameter %s already set as secret", rsc,
param);
- *exit_code = CRM_EX_EXISTS;
+ rc = EEXIST;
}
- rc = EINVAL;
goto done;
}
remainder = g_realloc(remainder, sizeof(gchar *) * 5);
remainder[3] = g_strdup(value);
remainder[4] = NULL;
- rc = subcommand_set(out, rsh_fn, rcp_fn, exit_code);
+ rc = subcommand_set(out, rsh_fn, rcp_fn);
done:
free(value);
return rc;
}
static int
-subcommand_sync(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
- crm_exit_t *exit_code)
+subcommand_sync(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
{
int rc = pcmk_rc_ok;
gchar *dirname = NULL;
char *cmdline = NULL;
gchar **peers = get_live_peers(out);
gchar *peer_str = NULL;
if (peers == NULL) {
return pcmk_rc_ok;
}
peer_str = g_strjoinv(" ", peers);
out->info(out, "Syncing %s to %s ...", PCMK__CIB_SECRETS_DIR, peer_str);
g_free(peer_str);
dirname = g_path_get_dirname(PCMK__CIB_SECRETS_DIR);
rc = rsh_fn(out, peers, "rm -rf " PCMK__CIB_SECRETS_DIR);
if (rc != pcmk_rc_ok) {
- *exit_code = CRM_EX_ERROR;
goto done;
}
cmdline = crm_strdup_printf("mkdir -p %s", dirname);
rc = rsh_fn(out, peers, cmdline);
free(cmdline);
if (rc != pcmk_rc_ok) {
- *exit_code = CRM_EX_ERROR;
goto done;
}
rc = rcp_fn(out, peers, dirname, PCMK__CIB_SECRETS_DIR);
- if (rc != pcmk_rc_ok) {
- *exit_code = CRM_EX_ERROR;
- }
-
done:
g_strfreev(peers);
g_free(dirname);
return rc;
}
static int
-subcommand_unstash(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn,
- crm_exit_t *exit_code)
+subcommand_unstash(pcmk__output_t *out, rsh_fn_t rsh_fn, rcp_fn_t rcp_fn)
{
int rc = pcmk_rc_ok;
const char *rsc = remainder[1];
const char *param = remainder[2];
char *local_value = NULL;
char *cib_value = NULL;
local_value = local_files_get(rsc, param);
if (local_value == NULL) {
out->err(out, "Nothing to unstash for resource %s parameter %s",
rsc, param);
- *exit_code = CRM_EX_NOSUCH;
- rc = EINVAL;
+ rc = ENOENT;
goto done;
}
if (check_cib_rsc(out, rsc) != pcmk_rc_ok) {
- *exit_code = CRM_EX_NOSUCH;
rc = ENODEV;
goto done;
}
cib_value = get_cib_param(out, rsc, param);
if (!is_secret(cib_value)) {
out->info(out, "Resource %s parameter %s is not set as secret, but we "
"have a local value so proceeding anyway", rsc, param);
}
rc = local_files_remove(out, rsh_fn, rcp_fn, rsc, param);
if (rc != pcmk_rc_ok) {
goto done;
}
rc = set_cib_param(out, rsc, param, local_value);
done:
free(cib_value);
free(local_value);
return rc;
}
static struct subcommand_entry subcommand_table[] = {
{ "check", 2, "check ", false,
subcommand_check },
{ "delete", 2, "delete ", false,
subcommand_delete },
{ "get", 2, "get ", false,
subcommand_get },
{ "set", 3, "set ", false,
subcommand_set },
{ "stash", 2, "stash ", true,
subcommand_stash },
{ "sync", 0, "sync", false, subcommand_sync },
{ "unstash", 2, "unstash ", true,
subcommand_unstash },
{ NULL },
};
static bool
tools_installed(pcmk__output_t *out, rsh_fn_t *rsh_fn, rcp_fn_t *rcp_fn,
GError **error)
{
gchar *path = NULL;
path = g_find_program_in_path("pssh");
if (path != NULL) {
g_free(path);
*rsh_fn = pssh;
*rcp_fn = pscp;
return true;
}
path = g_find_program_in_path("pdsh");
if (path != NULL) {
g_free(path);
*rsh_fn = pdsh;
*rcp_fn = pdcp;
return true;
}
path = g_find_program_in_path("ssh");
if (path != NULL) {
g_free(path);
*rsh_fn = ssh;
*rcp_fn = scp;
return true;
}
out->err(out, "Please install one of pssh, pdsh, or ssh");
return false;
}
static pcmk__supported_format_t formats[] = {
PCMK__SUPPORTED_FORMAT_NONE,
PCMK__SUPPORTED_FORMAT_TEXT,
PCMK__SUPPORTED_FORMAT_XML,
{ NULL, NULL, NULL }
};
static GOptionContext *
build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
const char *desc = NULL;
GOptionContext *context = NULL;
desc = "This command manages sensitive resource parameter values that should not be\n"
"stored directly in Pacemaker's Cluster Information Base (CIB). Such values\n"
"are handled by storing a special string directly in the CIB that tells\n"
"Pacemaker to look in a separate, protected file for the actual value.\n\n"
"The secret files are not encrypted, but protected by file system permissions\n"
"such that only root can read or modify them.\n\n"
"Since the secret files are stored locally, they must be synchronized across all\n"
"cluster nodes. This command handles the synchronization using (in order of\n"
"preference) pssh, pdsh, or ssh, so one of those must be installed. Before\n"
"synchronizing, this command will ping the cluster nodes to determine which are\n"
"alive, using fping if it is installed, otherwise the ping command. Installing\n"
"fping is strongly recommended for better performance.\n\n"
"Commands and their parameters:\n\n"
"check \n"
"\tVerify that the locally stored value of a sensitive resource parameter\n"
"\tmatches its locally stored MD5 hash.\n\n"
"delete \n"
"\tRemove a sensitive resource parameter value.\n\n"
"get \n"
"\tDisplay the locally stored value of a sensitive resource parameter.\n\n"
"set \n"
"\tSet the value of a sensitive resource parameter.\n\n"
"stash \n"
"\tMake a non-sensitive resource parameter that is already in the CIB\n"
"\tsensitive (move its value to a locally stored and protected file).\n"
"\tThis may not be used with -C.\n\n"
"sync\n"
"\tCopy all locally stored secrets to all other nodes.\n\n"
"unstash \n"
"\tMake a sensitive resource parameter that is already in the CIB\n"
"\tnon-sensitive (move its value from the locally stored file to the CIB).\n"
"\tThis may not be used with -C.\n\n\n"
"Known limitations:\n\n"
"This command can only be run from full cluster nodes (not Pacemaker Remote\n"
"nodes).\n\n"
"Changes are not atomic, so the cluster may use different values while a\n"
"change is in progress. To avoid problems, it is recommended to put the\n"
"cluster in maintenance mode when making changes with this command.\n\n"
"Changes in secret values do not trigger an agent reload or restart of the\n"
"affected resource, since they do not change the CIB. If a response is\n"
"desired before the next cluster recheck interval, any CIB change (such as\n"
"setting a node attribute) will trigger it.\n\n"
"If any node is down when changes to secrets are made, or a new node is\n"
"later added to the cluster, it may have different values when it joins the\n"
"cluster, before 'cibsecret sync' is run. To avoid this, it is recommended to\n"
"run the sync command (from another node) before starting Pacemaker on the\n"
"node.\n\n"
"Examples:\n\n"
"# cibsecret set ipmi_node1 passwd SecreT_PASS\n\n"
"# cibsecret get ipmi_node1 passwd\n\n"
"# cibsecret check ipmi_node1 passwd\n\n"
"# cibsecret stash ipmi_node2 passwd\n\n"
"# cibsecret sync\n";
context = pcmk__build_arg_context(args, "text (default), xml", group,
" [options]");
g_option_context_set_description(context, desc);
pcmk__add_main_args(context, entries);
return context;
}
int
main(int argc, char **argv)
{
crm_exit_t exit_code = CRM_EX_OK;
int rc = pcmk_rc_ok;
pcmk__output_t *out = NULL;
GError *error = NULL;
GOptionGroup *output_group = NULL;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
gchar **processed_args = pcmk__cmdline_preproc(argv, NULL);
GOptionContext *context = build_arg_context(args, &output_group);
struct subcommand_entry cmd;
rsh_fn_t rsh_fn;
rcp_fn_t rcp_fn;
pcmk__register_formats(output_group, formats);
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
exit_code = CRM_EX_USAGE;
goto done;
}
pcmk__cli_init_logging("cibsecret", args->verbosity);
rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
if (rc != pcmk_rc_ok) {
exit_code = CRM_EX_ERROR;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Error creating output format %s: %s", args->output_ty,
pcmk_rc_str(rc));
goto done;
}
if (args->version) {
out->version(out);
goto done;
}
/* No subcommand was given */
if ((remainder == NULL) || (g_strv_length(remainder) == 0)) {
gchar *help = g_option_context_get_help(context, TRUE, NULL);
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Must specify a command option\n\n%s", help);
g_free(help);
goto done;
}
/* Traverse the subcommand table looking for a match. */
for (int i = 0; i < PCMK__NELEM(subcommand_table); i++) {
cmd = subcommand_table[i];
if (!pcmk__str_eq(remainder[0], cmd.name, pcmk__str_none)) {
continue;
}
/* We found a match. Check that enough arguments were given and
* display a usage message if not. The "+ 1" is because the table
* entry lists how many arguments the subcommand takes, which does not
* include the subcommand itself.
*/
if (g_strv_length(remainder) != cmd.args + 1) {
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Usage: %s",
cmd.usage);
goto done;
}
/* We've found the subcommand handler and it's used correctly. */
break;
}
/* If we didn't find a match, a valid subcommand wasn't given. */
if (cmd.name == NULL) {
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Invalid subcommand given; valid subcommands: "
"check, delete, get, set, stash, sync, unstash");
goto done;
}
/* Check that we have the tools necessary to manage secrets */
if (!tools_installed(out, &rsh_fn, &rcp_fn, &error)) {
exit_code = CRM_EX_NOT_INSTALLED;
goto done;
}
/* Set a default umask so files we create are only accessible by the
* cluster user.
*/
umask(S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH);
/* Call the subcommand handler. If the handler fails, it will have already
* set exit_code to the reason why so there's no need to worry with
* additional error checking here at the moment.
*/
if (cmd.requires_cib && no_cib) {
out->err(out, "No access to Pacemaker, %s not supported", cmd.name);
exit_code = CRM_EX_USAGE;
goto done;
}
- cmd.handler(out, rsh_fn, rcp_fn, &exit_code);
+ rc = cmd.handler(out, rsh_fn, rcp_fn);
+ exit_code = pcmk_rc2exitc(rc);
done:
g_strfreev(processed_args);
g_strfreev(remainder);
pcmk__free_arg_context(context);
pcmk__output_and_clear_error(&error, out);
if (out != NULL) {
out->finish(out, exit_code, true, NULL);
pcmk__output_free(out);
}
pcmk__unregister_formats();
crm_exit(exit_code);
}
diff --git a/tools/crm_diff.c b/tools/crm_diff.c
index f9e645f78c..664efd5c0f 100644
--- a/tools/crm_diff.c
+++ b/tools/crm_diff.c
@@ -1,377 +1,376 @@
/*
* Copyright 2005-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include
#include // bool, true
#include // NULL, printf(), etc.
#include // free()
#include // GOption, etc.
#include // xmlNode
#include // xml_{create,apply}_patchset()
#define SUMMARY "Compare two Pacemaker configurations (in XML format) to " \
"produce a custom diff-like output, or apply such an output " \
"as a patch"
#define INDENT " "
struct {
gchar *source_file;
gchar *target_file;
gchar *source_string;
gchar *target_string;
bool patch;
gboolean as_cib;
gboolean no_version;
gboolean use_stdin; //!< \deprecated
} options;
static gboolean
patch_cb(const gchar *option_name, const gchar *optarg, gpointer data,
GError **error)
{
options.patch = true;
g_free(options.target_file);
options.target_file = g_strdup(optarg);
return TRUE;
}
/* @COMPAT Use last-one-wins for original/new/patch input sources
*
* @COMPAT Precedence is --original-string > --stdin > --original. --stdin is
* now deprecated and hidden, so we don't mention it in the help text.
*/
static GOptionEntry original_xml_entries[] = {
{ "original", 'o', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
&options.source_file,
"XML is contained in the named file. Currently --original-string\n"
INDENT "overrides this. In a future release, the last one specified\n"
INDENT "will be used.",
"FILE" },
{ "original-string", 'O', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
&options.source_string,
"XML is contained in the supplied string. Currently this takes\n"
INDENT "precedence over --original. In a future release, the last one\n"
INDENT "specified will be used.",
"STRING" },
{ NULL }
};
/* @COMPAT Precedence is --original-string > --stdin > --original. --stdin is
* now deprecated and hidden, so we don't mention it in the help text.
*/
static GOptionEntry operation_entries[] = {
{ "new", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.target_file,
"Compare the original XML to the contents of the named file. Currently\n"
INDENT "--new-string overrides this. In a future release, the last one\n"
INDENT "specified will be used.",
"FILE" },
{ "new-string", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING,
&options.target_string,
"Compare the original XML with the contents of the supplied string.\n"
INDENT "Currently this takes precedence over --patch and --new. In a\n"
INDENT "future release, the last one specified will be used.",
"STRING" },
{ "patch", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, patch_cb,
"Patch the original XML with the contents of the named file. Currently\n"
INDENT "--new-string and (if specified later) --new override the input\n"
INDENT "source specified here. In a future release, the last one\n"
INDENT "specified will be used. Note: even if this input source is\n"
INDENT "overridden, the input source will be applied as a patch to the\n"
INDENT "original XML.",
"FILE" },
{ NULL }
};
static GOptionEntry addl_entries[] = {
/* @FIXME Revisit --cib and --no-version and consider deprecating or
* renaming --cib, as well as improving the help text. --cib does things
* other than include version details. Also, even with --cib, we call
* xml_create_patchset() with manage_versions=false in generate_patch(). So
* we don't update the target versions in the patchset, and we don't drop
* the versions from the patchset unless --no-version is given.
*/
{ "cib", 'c', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.as_cib,
"Compare/patch the inputs as a CIB (includes version details)",
NULL },
{ "no-version", 'u', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
&options.no_version,
"Generate the difference without version details",
NULL },
// @COMPAT Deprecated
{ "stdin", 's', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_NONE, &options.use_stdin,
"Get the original XML and new (or patch) XML from stdin. Currently\n"
INDENT "--original-string and --new-string override this for original\n"
INDENT "and new/patch XML, respectively. In a future release, the last\n"
INDENT "one specified will be used.",
NULL },
{ NULL }
};
/*!
* \internal
* \brief Create an XML patchset from the given source and target XML trees
*
* \param[in,out] out Output object
* \param[in,out] source Source XML
* \param[in,out] target Target XML
* \param[in] as_cib If \c true, treat the XML trees as CIBs. In
* particular, ignore attribute position changes,
* include the target digest in the patchset, and log
* the source and target CIB versions.
* \param[in] no_version If \c true, ignore changes to the CIB version
* (must be \c false if \p as_cib is \c true)
*
* \return Standard Pacemaker return code
*/
static int
generate_patch(pcmk__output_t *out, xmlNode *source, xmlNode *target,
bool as_cib, bool no_version)
{
static const char *const vfields[] = {
PCMK_XA_ADMIN_EPOCH,
PCMK_XA_EPOCH,
PCMK_XA_NUM_UPDATES,
};
xmlNode *patchset = NULL;
GString *buffer = NULL;
// Currently impossible; just a reminder for when we move to libpacemaker
pcmk__assert(!as_cib || !no_version);
/* If we're ignoring the version, make the version information identical, so
* it isn't detected as a change.
*/
if (no_version) {
for (int i = 0; i < PCMK__NELEM(vfields); i++) {
pcmk__xe_set(target, vfields[i], pcmk__xe_get(source, vfields[i]));
}
}
if (as_cib) {
pcmk__xml_doc_set_flags(target->doc, pcmk__xf_ignore_attr_pos);
}
pcmk__xml_mark_changes(source, target);
crm_log_xml_debug(target, "target");
patchset = xml_create_patchset(0, source, target, NULL, false);
pcmk__log_xml_changes(LOG_INFO, target);
pcmk__xml_commit_changes(target->doc);
if (patchset == NULL) {
return pcmk_rc_ok; // No changes
}
if (as_cib) {
pcmk__xml_patchset_add_digest(patchset, target);
} else if (no_version) {
pcmk__xml_free(pcmk__xe_first_child(patchset, PCMK_XE_VERSION, NULL,
NULL));
}
pcmk__log_xml_patchset(LOG_NOTICE, patchset);
buffer = g_string_sized_new(1024);
pcmk__xml_string(patchset, pcmk__xml_fmt_pretty, buffer, 0);
out->output_xml(out, PCMK_XE_PATCHSET, buffer->str);
pcmk__xml_free(patchset);
g_string_free(buffer, TRUE);
/* pcmk_rc_error means there's a non-empty diff.
- * @COMPAT Choose a more descriptive return code, like one that maps to
- * CRM_EX_DIGEST?
+ * @COMPAT return pcmk_rc_digest_mismatch
*/
return pcmk_rc_error;
}
static const pcmk__supported_format_t formats[] = {
PCMK__SUPPORTED_FORMAT_NONE,
PCMK__SUPPORTED_FORMAT_TEXT,
PCMK__SUPPORTED_FORMAT_XML,
{ NULL, NULL, NULL }
};
static GOptionContext *
build_arg_context(pcmk__common_args_t *args, GOptionGroup **group)
{
GOptionContext *context = NULL;
const char *description = "Examples:\n\n"
"Obtain the two different configuration files by running cibadmin on the two cluster setups to compare:\n\n"
"\t# cibadmin --query > cib-old.xml\n\n"
"\t# cibadmin --query > cib-new.xml\n\n"
"Calculate and save the difference between the two files:\n\n"
"\t# crm_diff --original cib-old.xml --new cib-new.xml > patch.xml\n\n"
"Apply the patch to the original file:\n\n"
"\t# crm_diff --original cib-old.xml --patch patch.xml > updated.xml\n\n"
"Apply the patch to the running cluster:\n\n"
"\t# cibadmin --patch -x patch.xml\n";
context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
g_option_context_set_description(context, description);
pcmk__add_arg_group(context, "xml", "Original XML:",
"Show original XML options", original_xml_entries);
pcmk__add_arg_group(context, "operation", "Operation:",
"Show operation options", operation_entries);
pcmk__add_arg_group(context, "additional", "Additional Options:",
"Show additional options", addl_entries);
return context;
}
int
main(int argc, char **argv)
{
crm_exit_t exit_code = CRM_EX_OK;
int rc = pcmk_rc_ok;
xmlNode *source = NULL;
xmlNode *target = NULL;
pcmk__output_t *out = NULL;
GError *error = NULL;
GOptionGroup *output_group = NULL;
pcmk__common_args_t *args = pcmk__new_common_args(SUMMARY);
gchar **processed_args = pcmk__cmdline_preproc(argv, "nopNO");
GOptionContext *context = build_arg_context(args, &output_group);
pcmk__register_formats(output_group, formats);
if (!g_option_context_parse_strv(context, &processed_args, &error)) {
exit_code = CRM_EX_USAGE;
goto done;
}
pcmk__cli_init_logging("crm_diff", args->verbosity);
rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
if (rc != pcmk_rc_ok) {
exit_code = CRM_EX_ERROR;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Error creating output format %s: %s", args->output_ty,
pcmk_rc_str(rc));
goto done;
}
if (args->version) {
out->version(out);
goto done;
}
if (options.no_version) {
if (options.as_cib) {
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"-u/--no-version incompatible with -c/--cib");
goto done;
}
if (options.patch) {
out->err(out, "Warning: -u/--no-version ignored with -p/--patch");
}
}
if (options.source_string != NULL) {
source = pcmk__xml_parse(options.source_string);
} else if (options.use_stdin) {
source = pcmk__xml_read(NULL);
} else if (options.source_file != NULL) {
source = pcmk__xml_read(options.source_file);
} else {
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Either --original or --original-string must be specified");
goto done;
}
if (options.target_string != NULL) {
target = pcmk__xml_parse(options.target_string);
} else if (options.use_stdin) {
target = pcmk__xml_read(NULL);
} else if (options.target_file != NULL) {
target = pcmk__xml_read(options.target_file);
} else {
exit_code = CRM_EX_USAGE;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Either --new, --new-string, or --patch must be specified");
goto done;
}
if (source == NULL) {
exit_code = CRM_EX_DATAERR;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Failed to parse original XML");
goto done;
}
if (target == NULL) {
exit_code = CRM_EX_DATAERR;
g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
"Failed to parse %s XML", (options.patch? "patch" : "new"));
goto done;
}
if (options.patch) {
rc = xml_apply_patchset(source, target, options.as_cib);
rc = pcmk_legacy2rc(rc);
if (rc != pcmk_rc_ok) {
g_set_error(&error, PCMK__RC_ERROR, rc,
"Could not apply patch: %s", pcmk_rc_str(rc));
} else {
GString *buffer = g_string_sized_new(1024);
pcmk__xml_string(source, pcmk__xml_fmt_pretty, buffer, 0);
out->output_xml(out, PCMK_XE_UPDATED, buffer->str);
g_string_free(buffer, TRUE);
}
} else {
rc = generate_patch(out, source, target, options.as_cib,
options.no_version);
}
exit_code = pcmk_rc2exitc(rc);
done:
g_strfreev(processed_args);
pcmk__free_arg_context(context);
g_free(options.source_file);
g_free(options.target_file);
g_free(options.source_string);
g_free(options.target_string);
pcmk__xml_free(source);
pcmk__xml_free(target);
pcmk__output_and_clear_error(&error, out);
if (out != NULL) {
out->finish(out, exit_code, true, NULL);
pcmk__output_free(out);
}
pcmk__unregister_formats();
crm_exit(exit_code);
}