diff --git a/cts/cli/regression.error_codes.exp b/cts/cli/regression.error_codes.exp
index b3539aedd4..6c6f4e13a7 100644
--- a/cts/cli/regression.error_codes.exp
+++ b/cts/cli/regression.error_codes.exp
@@ -1,536 +1,544 @@
=#=#=#= 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) =#=#=#=
+-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) =#=#=#=
+-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
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
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 2669db92bc..224bcbeeb9 100644
--- a/include/crm/common/results.h
+++ b/include/crm/common/results.h
@@ -1,391 +1,393 @@
/*
- * Copyright 2012-2022 the Pacemaker project contributors
+ * Copyright 2012-2023 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
#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
# define CRM_ASSERT(expr) do { \
if (!(expr)) { \
crm_abort(__FILE__, __func__, __LINE__, #expr, TRUE, FALSE); \
abort(); /* crm_abort() doesn't always abort! */ \
} \
} while(0)
/*
* 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)
# 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
# define pcmk_err_diff_failed 206
# 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
/*!
* \enum pcmk_rc_e
* \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_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
pcmk_rc_ok = 0
// Positive values reserved for system error numbers
};
/*!
* \enum ocf_exitcode
* \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
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)
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
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
// Former Pacemaker extensions
PCMK_OCF_EXEC_ERROR = 192, //!< \deprecated (Unused)
PCMK_OCF_SIGNAL = 194, //!< \deprecated (Unused)
PCMK_OCF_NOT_SUPPORTED = 195, //!< \deprecated (Unused)
PCMK_OCF_PENDING = 196, //!< \deprecated (Unused)
PCMK_OCF_CANCELLED = 197, //!< \deprecated (Unused)
PCMK_OCF_TIMEOUT = 198, //!< \deprecated (Unused)
PCMK_OCF_OTHER_ERROR = 199, //!< \deprecated (Unused)
//! \deprecated Use PCMK_OCF_RUNNING_PROMOTED instead
PCMK_OCF_RUNNING_MASTER = PCMK_OCF_RUNNING_PROMOTED,
//! \deprecated Use PCMK_OCF_FAILED_PROMOTED instead
PCMK_OCF_FAILED_MASTER = PCMK_OCF_FAILED_PROMOTED,
//! \deprecated Use PCMK_OCF_DEGRADED_PROMOTED instead
PCMK_OCF_DEGRADED_MASTER = PCMK_OCF_DEGRADED_PROMOTED,
#endif
};
/*!
* \enum crm_exit_e
* \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.
*/
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
// 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;
/*!
* \enum pcmk_exec_status
* \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
};
/*!
* \enum pcmk_result_type
* \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);
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);
const char *pcmk_strerror(int rc);
const char *pcmk_errorname(int rc);
const char *bz2_strerror(int rc);
const char *crm_exit_name(crm_exit_t exit_code);
const char *crm_exit_str(crm_exit_t exit_code);
_Noreturn crm_exit_t crm_exit(crm_exit_t rc);
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 "complete";
case PCMK_EXEC_CANCELLED: return "Cancelled";
case PCMK_EXEC_TIMEOUT: return "Timed Out";
case PCMK_EXEC_NOT_SUPPORTED: return "NOT SUPPORTED";
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 "UNKNOWN!";
}
}
#ifdef __cplusplus
}
#endif
#endif
diff --git a/lib/common/io.c b/lib/common/io.c
index 986093b6b3..2264e16a83 100644
--- a/lib/common/io.c
+++ b/lib/common/io.c
@@ -1,663 +1,663 @@
/*
* Copyright 2004-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*!
* \internal
* \brief Create a directory, including any parent directories needed
*
* \param[in] path_c Pathname of the directory to create
* \param[in] mode Permissions to be used (with current umask) when creating
*
* \return Standard Pacemaker return code
*/
int
pcmk__build_path(const char *path_c, mode_t mode)
{
int offset = 1, len = 0;
int rc = pcmk_rc_ok;
char *path = strdup(path_c);
// cppcheck seems not to understand the abort logic in CRM_CHECK
// cppcheck-suppress memleak
CRM_CHECK(path != NULL, return -ENOMEM);
for (len = strlen(path); offset < len; offset++) {
if (path[offset] == '/') {
path[offset] = 0;
if ((mkdir(path, mode) < 0) && (errno != EEXIST)) {
rc = errno;
goto done;
}
path[offset] = '/';
}
}
if ((mkdir(path, mode) < 0) && (errno != EEXIST)) {
rc = errno;
}
done:
free(path);
return rc;
}
/*!
* \internal
* \brief Return canonicalized form of a path name
*
* \param[in] path Pathname to canonicalize
* \param[out] resolved_path Where to store canonicalized pathname
*
* \return Standard Pacemaker return code
* \note The caller is responsible for freeing \p resolved_path on success.
* \note This function exists because not all C library versions of
* realpath(path, resolved_path) support a NULL resolved_path.
*/
int
pcmk__real_path(const char *path, char **resolved_path)
{
CRM_CHECK((path != NULL) && (resolved_path != NULL), return EINVAL);
#if _POSIX_VERSION >= 200809L
/* Recent C libraries can dynamically allocate memory as needed */
*resolved_path = realpath(path, NULL);
return (*resolved_path == NULL)? errno : pcmk_rc_ok;
#elif defined(PATH_MAX)
/* Older implementations require pre-allocated memory */
/* (this is less desirable because PATH_MAX may be huge or not defined) */
*resolved_path = malloc(PATH_MAX);
if ((*resolved_path == NULL) || (realpath(path, *resolved_path) == NULL)) {
return errno;
}
return pcmk_rc_ok;
#else
*resolved_path = NULL;
return ENOTSUP;
#endif
}
/*!
* \internal
* \brief Create a file name using a sequence number
*
* \param[in] directory Directory that contains the file series
* \param[in] series Start of file name
* \param[in] sequence Sequence number
* \param[in] bzip Whether to use ".bz2" instead of ".raw" as extension
*
* \return Newly allocated file path (asserts on error, so always non-NULL)
* \note The caller is responsible for freeing the return value.
*/
char *
pcmk__series_filename(const char *directory, const char *series, int sequence,
bool bzip)
{
CRM_ASSERT((directory != NULL) && (series != NULL));
return crm_strdup_printf("%s/%s-%d.%s", directory, series, sequence,
(bzip? "bz2" : "raw"));
}
/*!
* \internal
* \brief Read sequence number stored in a file series' .last file
*
* \param[in] directory Directory that contains the file series
* \param[in] series Start of file name
* \param[out] seq Where to store the sequence number
*
* \return Standard Pacemaker return code
*/
int
pcmk__read_series_sequence(const char *directory, const char *series,
unsigned int *seq)
{
int rc;
FILE *fp = NULL;
char *series_file = NULL;
if ((directory == NULL) || (series == NULL) || (seq == NULL)) {
return EINVAL;
}
series_file = crm_strdup_printf("%s/%s.last", directory, series);
fp = fopen(series_file, "r");
if (fp == NULL) {
rc = errno;
crm_debug("Could not open series file %s: %s",
series_file, strerror(rc));
free(series_file);
return rc;
}
errno = 0;
if (fscanf(fp, "%u", seq) != 1) {
- rc = (errno == 0)? pcmk_rc_unknown_format : errno;
+ rc = (errno == 0)? ENODATA : errno;
crm_debug("Could not read sequence number from series file %s: %s",
series_file, pcmk_rc_str(rc));
fclose(fp);
return rc;
}
fclose(fp);
crm_trace("Found last sequence number %u in series file %s",
*seq, series_file);
free(series_file);
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Write sequence number to a file series' .last file
*
* \param[in] directory Directory that contains the file series
* \param[in] series Start of file name
* \param[in] sequence Sequence number to write
* \param[in] max Maximum sequence value, after which it is reset to 0
*
* \note This function logs some errors but does not return any to the caller
*/
void
pcmk__write_series_sequence(const char *directory, const char *series,
unsigned int sequence, int max)
{
int rc = 0;
FILE *file_strm = NULL;
char *series_file = NULL;
CRM_CHECK(directory != NULL, return);
CRM_CHECK(series != NULL, return);
if (max == 0) {
return;
}
if (max > 0 && sequence >= max) {
sequence = 0;
}
series_file = crm_strdup_printf("%s/%s.last", directory, series);
file_strm = fopen(series_file, "w");
if (file_strm != NULL) {
rc = fprintf(file_strm, "%u", sequence);
if (rc < 0) {
crm_perror(LOG_ERR, "Cannot write to series file %s", series_file);
}
} else {
crm_err("Cannot open series file %s for writing", series_file);
}
if (file_strm != NULL) {
fflush(file_strm);
fclose(file_strm);
}
crm_trace("Wrote %d to %s", sequence, series_file);
free(series_file);
}
/*!
* \internal
* \brief Change the owner and group of a file series' .last file
*
* \param[in] directory Directory that contains series
* \param[in] series Series to change
* \param[in] uid User ID of desired file owner
* \param[in] gid Group ID of desired file group
*
* \return Standard Pacemaker return code
* \note The caller must have the appropriate privileges.
*/
int
pcmk__chown_series_sequence(const char *directory, const char *series,
uid_t uid, gid_t gid)
{
char *series_file = NULL;
int rc = pcmk_rc_ok;
if ((directory == NULL) || (series == NULL)) {
return EINVAL;
}
series_file = crm_strdup_printf("%s/%s.last", directory, series);
if (chown(series_file, uid, gid) < 0) {
rc = errno;
}
free(series_file);
return rc;
}
static bool
pcmk__daemon_user_can_write(const char *target_name, struct stat *target_stat)
{
struct passwd *sys_user = NULL;
errno = 0;
sys_user = getpwnam(CRM_DAEMON_USER);
if (sys_user == NULL) {
crm_notice("Could not find user %s: %s",
CRM_DAEMON_USER, pcmk_rc_str(errno));
return FALSE;
}
if (target_stat->st_uid != sys_user->pw_uid) {
crm_notice("%s is not owned by user %s " CRM_XS " uid %d != %d",
target_name, CRM_DAEMON_USER, sys_user->pw_uid,
target_stat->st_uid);
return FALSE;
}
if ((target_stat->st_mode & (S_IRUSR | S_IWUSR)) == 0) {
crm_notice("%s is not readable and writable by user %s "
CRM_XS " st_mode=0%lo",
target_name, CRM_DAEMON_USER,
(unsigned long) target_stat->st_mode);
return FALSE;
}
return TRUE;
}
static bool
pcmk__daemon_group_can_write(const char *target_name, struct stat *target_stat)
{
struct group *sys_grp = NULL;
errno = 0;
sys_grp = getgrnam(CRM_DAEMON_GROUP);
if (sys_grp == NULL) {
crm_notice("Could not find group %s: %s",
CRM_DAEMON_GROUP, pcmk_rc_str(errno));
return FALSE;
}
if (target_stat->st_gid != sys_grp->gr_gid) {
crm_notice("%s is not owned by group %s " CRM_XS " uid %d != %d",
target_name, CRM_DAEMON_GROUP,
sys_grp->gr_gid, target_stat->st_gid);
return FALSE;
}
if ((target_stat->st_mode & (S_IRGRP | S_IWGRP)) == 0) {
crm_notice("%s is not readable and writable by group %s "
CRM_XS " st_mode=0%lo",
target_name, CRM_DAEMON_GROUP,
(unsigned long) target_stat->st_mode);
return FALSE;
}
return TRUE;
}
/*!
* \internal
* \brief Check whether a directory or file is writable by the cluster daemon
*
* Return true if either the cluster daemon user or cluster daemon group has
* write permission on a specified file or directory.
*
* \param[in] dir Directory to check (this argument must be specified, and
* the directory must exist)
* \param[in] file File to check (only the directory will be checked if this
* argument is not specified or the file does not exist)
*
* \return true if target is writable by cluster daemon, false otherwise
*/
bool
pcmk__daemon_can_write(const char *dir, const char *file)
{
int s_res = 0;
struct stat buf;
char *full_file = NULL;
const char *target = NULL;
// Caller must supply directory
CRM_ASSERT(dir != NULL);
// If file is given, check whether it exists as a regular file
if (file != NULL) {
full_file = crm_strdup_printf("%s/%s", dir, file);
target = full_file;
s_res = stat(full_file, &buf);
if (s_res < 0) {
crm_notice("%s not found: %s", target, pcmk_rc_str(errno));
free(full_file);
full_file = NULL;
target = NULL;
} else if (S_ISREG(buf.st_mode) == FALSE) {
crm_err("%s must be a regular file " CRM_XS " st_mode=0%lo",
target, (unsigned long) buf.st_mode);
free(full_file);
return false;
}
}
// If file is not given, ensure dir exists as directory
if (target == NULL) {
target = dir;
s_res = stat(dir, &buf);
if (s_res < 0) {
crm_err("%s not found: %s", dir, pcmk_rc_str(errno));
return false;
} else if (S_ISDIR(buf.st_mode) == FALSE) {
crm_err("%s must be a directory " CRM_XS " st_mode=0%lo",
dir, (unsigned long) buf.st_mode);
return false;
}
}
if (!pcmk__daemon_user_can_write(target, &buf)
&& !pcmk__daemon_group_can_write(target, &buf)) {
crm_err("%s must be owned and writable by either user %s or group %s "
CRM_XS " st_mode=0%lo",
target, CRM_DAEMON_USER, CRM_DAEMON_GROUP,
(unsigned long) buf.st_mode);
free(full_file);
return false;
}
free(full_file);
return true;
}
/*!
* \internal
* \brief Flush and sync a directory to disk
*
* \param[in] name Directory to flush and sync
* \note This function logs errors but does not return them to the caller
*/
void
pcmk__sync_directory(const char *name)
{
int fd;
DIR *directory;
directory = opendir(name);
if (directory == NULL) {
crm_perror(LOG_ERR, "Could not open %s for syncing", name);
return;
}
fd = dirfd(directory);
if (fd < 0) {
crm_perror(LOG_ERR, "Could not obtain file descriptor for %s", name);
return;
}
if (fsync(fd) < 0) {
crm_perror(LOG_ERR, "Could not sync %s", name);
}
if (closedir(directory) < 0) {
crm_perror(LOG_ERR, "Could not close %s after fsync", name);
}
}
/*!
* \internal
* \brief Read the contents of a file
*
* \param[in] filename Name of file to read
* \param[out] contents Where to store file contents
*
* \return Standard Pacemaker return code
* \note On success, the caller is responsible for freeing contents.
*/
int
pcmk__file_contents(const char *filename, char **contents)
{
FILE *fp;
int length, read_len;
int rc = pcmk_rc_ok;
if ((filename == NULL) || (contents == NULL)) {
return EINVAL;
}
fp = fopen(filename, "r");
if ((fp == NULL) || (fseek(fp, 0L, SEEK_END) < 0)) {
rc = errno;
goto bail;
}
length = ftell(fp);
if (length < 0) {
rc = errno;
goto bail;
}
if (length == 0) {
*contents = NULL;
} else {
*contents = calloc(length + 1, sizeof(char));
if (*contents == NULL) {
rc = errno;
goto bail;
}
rewind(fp);
read_len = fread(*contents, 1, length, fp); /* Coverity: False positive */
if (read_len != length) {
free(*contents);
*contents = NULL;
rc = EIO;
}
}
bail:
if (fp != NULL) {
fclose(fp);
}
return rc;
}
/*!
* \internal
* \brief Write text to a file, flush and sync it to disk, then close the file
*
* \param[in] fd File descriptor opened for writing
* \param[in] contents String to write to file
*
* \return Standard Pacemaker return code
*/
int
pcmk__write_sync(int fd, const char *contents)
{
int rc = 0;
FILE *fp = fdopen(fd, "w");
if (fp == NULL) {
return errno;
}
if ((contents != NULL) && (fprintf(fp, "%s", contents) < 0)) {
rc = EIO;
}
if (fflush(fp) != 0) {
rc = errno;
}
if (fsync(fileno(fp)) < 0) {
rc = errno;
}
fclose(fp);
return rc;
}
/*!
* \internal
* \brief Set a file descriptor to non-blocking
*
* \param[in] fd File descriptor to use
*
* \return Standard Pacemaker return code
*/
int
pcmk__set_nonblocking(int fd)
{
int flag = fcntl(fd, F_GETFL);
if (flag < 0) {
return errno;
}
if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0) {
return errno;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Get directory name for temporary files
*
* Return the value of the TMPDIR environment variable if it is set to a
* full path, otherwise return "/tmp".
*
* \return Name of directory to be used for temporary files
*/
const char *
pcmk__get_tmpdir(void)
{
const char *dir = getenv("TMPDIR");
return (dir && (*dir == '/'))? dir : "/tmp";
}
/*!
* \internal
* \brief Close open file descriptors
*
* Close all file descriptors (except optionally stdin, stdout, and stderr),
* which is a best practice for a new child process forked for the purpose of
* executing an external program.
*
* \param[in] bool If true, close stdin, stdout, and stderr as well
*/
void
pcmk__close_fds_in_child(bool all)
{
DIR *dir;
struct rlimit rlim;
rlim_t max_fd;
int min_fd = (all? 0 : (STDERR_FILENO + 1));
/* Find the current process's (soft) limit for open files. getrlimit()
* should always work, but have a fallback just in case.
*/
if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) {
max_fd = rlim.rlim_cur - 1;
} else {
long conf_max = sysconf(_SC_OPEN_MAX);
max_fd = (conf_max > 0)? conf_max : 1024;
}
/* /proc/self/fd (on Linux) or /dev/fd (on most OSes) contains symlinks to
* all open files for the current process, named as the file descriptor.
* Use this if available, because it's more efficient than a shotgun
* approach to closing descriptors.
*/
#if HAVE_LINUX_PROCFS
dir = opendir("/proc/self/fd");
if (dir == NULL) {
dir = opendir("/dev/fd");
}
#else
dir = opendir("/dev/fd");
#endif // HAVE_LINUX_PROCFS
if (dir != NULL) {
struct dirent *entry;
int dir_fd = dirfd(dir);
while ((entry = readdir(dir)) != NULL) {
int lpc = atoi(entry->d_name);
/* How could one of these entries be higher than max_fd, you ask?
* It isn't possible in normal operation, but when run under
* valgrind, valgrind can open high-numbered file descriptors for
* its own use that are higher than the process's soft limit.
* These will show up in the fd directory but aren't closable.
*/
if ((lpc >= min_fd) && (lpc <= max_fd) && (lpc != dir_fd)) {
close(lpc);
}
}
closedir(dir);
return;
}
/* If no fd directory is available, iterate over all possible descriptors.
* This is less efficient due to the overhead of many system calls.
*/
for (int lpc = max_fd; lpc >= min_fd; lpc--) {
close(lpc);
}
}
/*!
* \brief Duplicate a file path, inserting a prefix if not absolute
*
* \param[in] filename File path to duplicate
* \param[in] dirname If filename is not absolute, prefix to add
*
* \return Newly allocated memory with full path (guaranteed non-NULL)
*/
char *
pcmk__full_path(const char *filename, const char *dirname)
{
char *path = NULL;
CRM_ASSERT(filename != NULL);
if (filename[0] == '/') {
path = strdup(filename);
CRM_ASSERT(path != NULL);
} else {
CRM_ASSERT(dirname != NULL);
path = crm_strdup_printf("%s/%s", dirname, filename);
}
return path;
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include
void
crm_build_path(const char *path_c, mode_t mode)
{
int rc = pcmk__build_path(path_c, mode);
if (rc != pcmk_rc_ok) {
crm_err("Could not create directory '%s': %s",
path_c, pcmk_rc_str(rc));
}
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/nvpair.c b/lib/common/nvpair.c
index d79cdea748..3766c459ab 100644
--- a/lib/common/nvpair.c
+++ b/lib/common/nvpair.c
@@ -1,992 +1,992 @@
/*
- * Copyright 2004-2022 the Pacemaker project contributors
+ * Copyright 2004-2023 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
#include "crmcommon_private.h"
/*
* This file isolates handling of three types of name/value pairs:
*
* - pcmk_nvpair_t data type
* - XML attributes ()
* - XML nvpair elements ()
*/
// pcmk_nvpair_t handling
/*!
* \internal
* \brief Allocate a new name/value pair
*
* \param[in] name New name (required)
* \param[in] value New value
*
* \return Newly allocated name/value pair
* \note The caller is responsible for freeing the result with
* \c pcmk__free_nvpair().
*/
static pcmk_nvpair_t *
pcmk__new_nvpair(const char *name, const char *value)
{
pcmk_nvpair_t *nvpair = NULL;
CRM_ASSERT(name);
nvpair = calloc(1, sizeof(pcmk_nvpair_t));
CRM_ASSERT(nvpair);
pcmk__str_update(&nvpair->name, name);
pcmk__str_update(&nvpair->value, value);
return nvpair;
}
/*!
* \internal
* \brief Free a name/value pair
*
* \param[in,out] nvpair Name/value pair to free
*/
static void
pcmk__free_nvpair(gpointer data)
{
if (data) {
pcmk_nvpair_t *nvpair = data;
free(nvpair->name);
free(nvpair->value);
free(nvpair);
}
}
/*!
* \brief Prepend a name/value pair to a list
*
* \param[in,out] nvpairs List to modify
* \param[in] name New entry's name
* \param[in] value New entry's value
*
* \return New head of list
* \note The caller is responsible for freeing the list with
* \c pcmk_free_nvpairs().
*/
GSList *
pcmk_prepend_nvpair(GSList *nvpairs, const char *name, const char *value)
{
return g_slist_prepend(nvpairs, pcmk__new_nvpair(name, value));
}
/*!
* \brief Free a list of name/value pairs
*
* \param[in,out] list List to free
*/
void
pcmk_free_nvpairs(GSList *nvpairs)
{
g_slist_free_full(nvpairs, pcmk__free_nvpair);
}
/*!
* \internal
* \brief Compare two name/value pairs
*
* \param[in] a First name/value pair to compare
* \param[in] b Second name/value pair to compare
*
* \return 0 if a == b, 1 if a > b, -1 if a < b
*/
static gint
pcmk__compare_nvpair(gconstpointer a, gconstpointer b)
{
int rc = 0;
const pcmk_nvpair_t *pair_a = a;
const pcmk_nvpair_t *pair_b = b;
CRM_ASSERT(a != NULL);
CRM_ASSERT(pair_a->name != NULL);
CRM_ASSERT(b != NULL);
CRM_ASSERT(pair_b->name != NULL);
rc = strcmp(pair_a->name, pair_b->name);
if (rc < 0) {
return -1;
} else if (rc > 0) {
return 1;
}
return 0;
}
/*!
* \brief Sort a list of name/value pairs
*
* \param[in,out] list List to sort
*
* \return New head of list
*/
GSList *
pcmk_sort_nvpairs(GSList *list)
{
return g_slist_sort(list, pcmk__compare_nvpair);
}
/*!
* \brief Create a list of name/value pairs from an XML node's attributes
*
* \param[in] XML to parse
*
* \return New list of name/value pairs
* \note It is the caller's responsibility to free the list with
* \c pcmk_free_nvpairs().
*/
GSList *
pcmk_xml_attrs2nvpairs(const xmlNode *xml)
{
GSList *result = NULL;
for (xmlAttrPtr iter = pcmk__xe_first_attr(xml); iter != NULL;
iter = iter->next) {
result = pcmk_prepend_nvpair(result,
(const char *) iter->name,
(const char *) pcmk__xml_attr_value(iter));
}
return result;
}
/*!
* \internal
* \brief Add an XML attribute corresponding to a name/value pair
*
* Suitable for glib list iterators, this function adds a NAME=VALUE
* XML attribute based on a given name/value pair.
*
* \param[in] data Name/value pair
* \param[out] user_data XML node to add attributes to
*/
static void
pcmk__nvpair_add_xml_attr(gpointer data, gpointer user_data)
{
pcmk_nvpair_t *pair = data;
xmlNode *parent = user_data;
crm_xml_add(parent, pair->name, pair->value);
}
/*!
* \brief Add XML attributes based on a list of name/value pairs
*
* \param[in,out] list List of name/value pairs
* \param[in,out] xml XML node to add attributes to
*/
void
pcmk_nvpairs2xml_attrs(GSList *list, xmlNode *xml)
{
g_slist_foreach(list, pcmk__nvpair_add_xml_attr, xml);
}
// convenience function for name=value strings
/*!
* \internal
* \brief Extract the name and value from an input string formatted as "name=value".
* If unable to extract them, they are returned as NULL.
*
* \param[in] input The input string, likely from the command line
* \param[out] name Everything before the first '=' in the input string
* \param[out] value Everything after the first '=' in the input string
*
* \return 2 if both name and value could be extracted, 1 if only one could, and
* and error code otherwise
*/
int
pcmk__scan_nvpair(const char *input, char **name, char **value)
{
#ifdef HAVE_SSCANF_M
*name = NULL;
*value = NULL;
if (sscanf(input, "%m[^=]=%m[^\n]", name, value) <= 0) {
return -pcmk_err_bad_nvpair;
}
#else
char *sep = NULL;
*name = NULL;
*value = NULL;
sep = strstr(optarg, "=");
if (sep == NULL) {
return -pcmk_err_bad_nvpair;
}
*name = strndup(input, sep-input);
if (*name == NULL) {
return -ENOMEM;
}
/* If the last char in optarg is =, the user gave no
* value for the option. Leave it as NULL.
*/
if (*(sep+1) != '\0') {
*value = strdup(sep+1);
if (*value == NULL) {
return -ENOMEM;
}
}
#endif
if (*name != NULL && *value != NULL) {
return 2;
} else if (*name != NULL || *value != NULL) {
return 1;
} else {
return -pcmk_err_bad_nvpair;
}
}
/*!
* \internal
* \brief Format a name/value pair.
*
* Units can optionally be provided for the value. Note that unlike most
* formatting functions, this one returns the formatted string. It is
* assumed that the most common use of this function will be to build up
* a string to be output as part of other functions.
*
* \note The caller is responsible for freeing the return value after use.
*
* \param[in] name The name of the nvpair.
* \param[in] value The value of the nvpair.
* \param[in] units Optional units for the value, or NULL.
*
* \return Newly allocated string with name/value pair
*/
char *
pcmk__format_nvpair(const char *name, const char *value, const char *units)
{
return crm_strdup_printf("%s=\"%s%s\"", name, value, units ? units : "");
}
// XML attribute handling
/*!
* \brief Create an XML attribute with specified name and value
*
* \param[in,out] node XML node to modify
* \param[in] name Attribute name to set
* \param[in] value Attribute value to set
*
* \return New value on success, \c NULL otherwise
* \note This does nothing if node, name, or value are \c NULL or empty.
*/
const char *
crm_xml_add(xmlNode *node, const char *name, const char *value)
{
bool dirty = FALSE;
xmlAttr *attr = NULL;
CRM_CHECK(node != NULL, return NULL);
CRM_CHECK(name != NULL, return NULL);
if (value == NULL) {
return NULL;
}
if (pcmk__tracking_xml_changes(node, FALSE)) {
const char *old = crm_element_value(node, name);
if (old == NULL || value == NULL || strcmp(old, value) != 0) {
dirty = TRUE;
}
}
if (dirty && (pcmk__check_acl(node, name, pcmk__xf_acl_create) == FALSE)) {
crm_trace("Cannot add %s=%s to %s", name, value, node->name);
return NULL;
}
attr = xmlSetProp(node, (pcmkXmlStr) name, (pcmkXmlStr) value);
if (dirty) {
pcmk__mark_xml_attr_dirty(attr);
}
CRM_CHECK(attr && attr->children && attr->children->content, return NULL);
return (char *)attr->children->content;
}
/*!
* \brief Replace an XML attribute with specified name and (possibly NULL) value
*
* \param[in,out] node XML node to modify
* \param[in] name Attribute name to set
* \param[in] value Attribute value to set
*
* \return New value on success, \c NULL otherwise
* \note This does nothing if node or name is \c NULL or empty.
*/
const char *
crm_xml_replace(xmlNode *node, const char *name, const char *value)
{
bool dirty = FALSE;
xmlAttr *attr = NULL;
const char *old_value = NULL;
CRM_CHECK(node != NULL, return NULL);
CRM_CHECK(name != NULL && name[0] != 0, return NULL);
old_value = crm_element_value(node, name);
/* Could be re-setting the same value */
CRM_CHECK(old_value != value, return value);
if (pcmk__check_acl(node, name, pcmk__xf_acl_write) == FALSE) {
/* Create a fake object linked to doc->_private instead? */
crm_trace("Cannot replace %s=%s to %s", name, value, node->name);
return NULL;
} else if (old_value && !value) {
xml_remove_prop(node, name);
return NULL;
}
if (pcmk__tracking_xml_changes(node, FALSE)) {
if (!old_value || !value || !strcmp(old_value, value)) {
dirty = TRUE;
}
}
attr = xmlSetProp(node, (pcmkXmlStr) name, (pcmkXmlStr) value);
if (dirty) {
pcmk__mark_xml_attr_dirty(attr);
}
CRM_CHECK(attr && attr->children && attr->children->content, return NULL);
return (char *) attr->children->content;
}
/*!
* \brief Create an XML attribute with specified name and integer value
*
* This is like \c crm_xml_add() but taking an integer value.
*
* \param[in,out] node XML node to modify
* \param[in] name Attribute name to set
* \param[in] value Attribute value to set
*
* \return New value as string on success, \c NULL otherwise
* \note This does nothing if node or name are \c NULL or empty.
*/
const char *
crm_xml_add_int(xmlNode *node, const char *name, int value)
{
char *number = pcmk__itoa(value);
const char *added = crm_xml_add(node, name, number);
free(number);
return added;
}
/*!
* \brief Create an XML attribute with specified name and unsigned value
*
* This is like \c crm_xml_add() but taking a guint value.
*
* \param[in,out] node XML node to modify
* \param[in] name Attribute name to set
* \param[in] ms Attribute value to set
*
* \return New value as string on success, \c NULL otherwise
* \note This does nothing if node or name are \c NULL or empty.
*/
const char *
crm_xml_add_ms(xmlNode *node, const char *name, guint ms)
{
char *number = crm_strdup_printf("%u", ms);
const char *added = crm_xml_add(node, name, number);
free(number);
return added;
}
// Maximum size of null-terminated string representation of 64-bit integer
// -9223372036854775808
#define LLSTRSIZE 21
/*!
* \brief Create an XML attribute with specified name and long long int value
*
* This is like \c crm_xml_add() but taking a long long int value. It is a
* useful equivalent for defined types like time_t, etc.
*
* \param[in,out] xml XML node to modify
* \param[in] name Attribute name to set
* \param[in] value Attribute value to set
*
* \return New value as string on success, \c NULL otherwise
* \note This does nothing if xml or name are \c NULL or empty.
* This does not support greater than 64-bit values.
*/
const char *
crm_xml_add_ll(xmlNode *xml, const char *name, long long value)
{
char s[LLSTRSIZE] = { '\0', };
if (snprintf(s, LLSTRSIZE, "%lld", (long long) value) == LLSTRSIZE) {
return NULL;
}
return crm_xml_add(xml, name, s);
}
/*!
* \brief Create XML attributes for seconds and microseconds
*
* This is like \c crm_xml_add() but taking a struct timeval.
*
* \param[in,out] xml XML node to modify
* \param[in] name_sec Name of XML attribute for seconds
* \param[in] name_usec Name of XML attribute for microseconds (or NULL)
* \param[in] value Time value to set
*
* \return New seconds value as string on success, \c NULL otherwise
* \note This does nothing if xml, name_sec, or value is \c NULL.
*/
const char *
crm_xml_add_timeval(xmlNode *xml, const char *name_sec, const char *name_usec,
const struct timeval *value)
{
const char *added = NULL;
if (xml && name_sec && value) {
added = crm_xml_add_ll(xml, name_sec, (long long) value->tv_sec);
if (added && name_usec) {
// Any error is ignored (we successfully added seconds)
crm_xml_add_ll(xml, name_usec, (long long) value->tv_usec);
}
}
return added;
}
/*!
* \brief Retrieve the value of an XML attribute
*
* \param[in] data XML node to check
* \param[in] name Attribute name to check
*
* \return Value of specified attribute (may be \c NULL)
*/
const char *
crm_element_value(const xmlNode *data, const char *name)
{
xmlAttr *attr = NULL;
if (data == NULL) {
crm_err("Couldn't find %s in NULL", name ? name : "");
CRM_LOG_ASSERT(data != NULL);
return NULL;
} else if (name == NULL) {
crm_err("Couldn't find NULL in %s", crm_element_name(data));
return NULL;
}
/* The first argument to xmlHasProp() has always been const,
* but libxml2 <2.9.2 didn't declare that, so cast it
*/
attr = xmlHasProp((xmlNode *) data, (pcmkXmlStr) name);
if (!attr || !attr->children) {
return NULL;
}
return (const char *) attr->children->content;
}
/*!
* \brief Retrieve the integer value of an XML attribute
*
* This is like \c crm_element_value() but getting the value as an integer.
*
* \param[in] data XML node to check
* \param[in] name Attribute name to check
* \param[out] dest Where to store element value
*
* \return 0 on success, -1 otherwise
*/
int
crm_element_value_int(const xmlNode *data, const char *name, int *dest)
{
const char *value = NULL;
CRM_CHECK(dest != NULL, return -1);
value = crm_element_value(data, name);
if (value) {
long long value_ll;
if ((pcmk__scan_ll(value, &value_ll, 0LL) != pcmk_rc_ok)
|| (value_ll < INT_MIN) || (value_ll > INT_MAX)) {
*dest = PCMK__PARSE_INT_DEFAULT;
} else {
*dest = (int) value_ll;
return 0;
}
}
return -1;
}
/*!
* \brief Retrieve the long long integer value of an XML attribute
*
* This is like \c crm_element_value() but getting the value as a long long int.
*
* \param[in] data XML node to check
* \param[in] name Attribute name to check
* \param[out] dest Where to store element value
*
* \return 0 on success, -1 otherwise
*/
int
crm_element_value_ll(const xmlNode *data, const char *name, long long *dest)
{
const char *value = NULL;
CRM_CHECK(dest != NULL, return -1);
value = crm_element_value(data, name);
if ((value != NULL)
&& (pcmk__scan_ll(value, dest, PCMK__PARSE_INT_DEFAULT) == pcmk_rc_ok)) {
return 0;
}
return -1;
}
/*!
* \brief Retrieve the millisecond value of an XML attribute
*
* This is like \c crm_element_value() but returning the value as a guint.
*
* \param[in] data XML node to check
* \param[in] name Attribute name to check
* \param[out] dest Where to store attribute value
*
* \return \c pcmk_ok on success, -1 otherwise
*/
int
crm_element_value_ms(const xmlNode *data, const char *name, guint *dest)
{
const char *value = NULL;
long long value_ll;
CRM_CHECK(dest != NULL, return -1);
*dest = 0;
value = crm_element_value(data, name);
if ((pcmk__scan_ll(value, &value_ll, 0LL) != pcmk_rc_ok)
|| (value_ll < 0) || (value_ll > G_MAXUINT)) {
return -1;
}
*dest = (guint) value_ll;
return pcmk_ok;
}
/*!
* \brief Retrieve the seconds-since-epoch value of an XML attribute
*
* This is like \c crm_element_value() but returning the value as a time_t.
*
* \param[in] xml XML node to check
* \param[in] name Attribute name to check
* \param[out] dest Where to store attribute value
*
* \return \c pcmk_ok on success, -1 otherwise
*/
int
crm_element_value_epoch(const xmlNode *xml, const char *name, time_t *dest)
{
long long value_ll = 0;
if (crm_element_value_ll(xml, name, &value_ll) < 0) {
return -1;
}
/* Unfortunately, we can't do any bounds checking, since time_t has neither
* standardized bounds nor constants defined for them.
*/
*dest = (time_t) value_ll;
return pcmk_ok;
}
/*!
* \brief Retrieve the value of XML second/microsecond attributes as time
*
* This is like \c crm_element_value() but returning value as a struct timeval.
*
* \param[in] xml XML to parse
* \param[in] name_sec Name of XML attribute for seconds
* \param[in] name_usec Name of XML attribute for microseconds
* \param[out] dest Where to store result
*
* \return \c pcmk_ok on success, -errno on error
* \note Values default to 0 if XML or XML attribute does not exist
*/
int
crm_element_value_timeval(const xmlNode *xml, const char *name_sec,
const char *name_usec, struct timeval *dest)
{
long long value_i = 0;
CRM_CHECK(dest != NULL, return -EINVAL);
dest->tv_sec = 0;
dest->tv_usec = 0;
if (xml == NULL) {
return pcmk_ok;
}
/* Unfortunately, we can't do any bounds checking, since there are no
* constants provided for the bounds of time_t and suseconds_t, and
* calculating them isn't worth the effort. If there are XML values
* beyond the native sizes, there will probably be worse problems anyway.
*/
// Parse seconds
errno = 0;
if (crm_element_value_ll(xml, name_sec, &value_i) < 0) {
return -errno;
}
dest->tv_sec = (time_t) value_i;
// Parse microseconds
if (crm_element_value_ll(xml, name_usec, &value_i) < 0) {
return -errno;
}
dest->tv_usec = (suseconds_t) value_i;
return pcmk_ok;
}
/*!
* \brief Retrieve a copy of the value of an XML attribute
*
* This is like \c crm_element_value() but allocating new memory for the result.
*
* \param[in] data XML node to check
* \param[in] name Attribute name to check
*
* \return Value of specified attribute (may be \c NULL)
* \note The caller is responsible for freeing the result.
*/
char *
crm_element_value_copy(const xmlNode *data, const char *name)
{
char *value_copy = NULL;
pcmk__str_update(&value_copy, crm_element_value(data, name));
return value_copy;
}
/*!
* \brief Add hash table entry to XML as (possibly legacy) name/value
*
* Suitable for \c g_hash_table_foreach(), this function takes a hash table key
* and value, with an XML node passed as user data, and adds an XML attribute
* with the specified name and value if it does not already exist. If the key
* name starts with a digit, this will instead add a \ child to the XML (for legacy compatibility with heartbeat).
*
* \param[in] key Key of hash table entry
* \param[in] value Value of hash table entry
* \param[in,out] user_data XML node
*/
void
hash2smartfield(gpointer key, gpointer value, gpointer user_data)
{
const char *name = key;
const char *s_value = value;
xmlNode *xml_node = user_data;
if (isdigit(name[0])) {
xmlNode *tmp = create_xml_node(xml_node, XML_TAG_PARAM);
crm_xml_add(tmp, XML_NVPAIR_ATTR_NAME, name);
crm_xml_add(tmp, XML_NVPAIR_ATTR_VALUE, s_value);
} else if (crm_element_value(xml_node, name) == NULL) {
crm_xml_add(xml_node, name, s_value);
crm_trace("dumped: %s=%s", name, s_value);
} else {
crm_trace("duplicate: %s=%s", name, s_value);
}
}
/*!
* \brief Set XML attribute based on hash table entry
*
* Suitable for \c g_hash_table_foreach(), this function takes a hash table key
* and value, with an XML node passed as user data, and adds an XML attribute
* with the specified name and value if it does not already exist.
*
* \param[in] key Key of hash table entry
* \param[in] value Value of hash table entry
* \param[in,out] user_data XML node
*/
void
hash2field(gpointer key, gpointer value, gpointer user_data)
{
const char *name = key;
const char *s_value = value;
xmlNode *xml_node = user_data;
if (crm_element_value(xml_node, name) == NULL) {
crm_xml_add(xml_node, name, s_value);
} else {
crm_trace("duplicate: %s=%s", name, s_value);
}
}
/*!
* \brief Set XML attribute based on hash table entry, as meta-attribute name
*
* Suitable for \c g_hash_table_foreach(), this function takes a hash table key
* and value, with an XML node passed as user data, and adds an XML attribute
* with the meta-attribute version of the specified name and value if it does
* not already exist and if the name does not appear to be cluster-internal.
*
* \param[in] key Key of hash table entry
* \param[in] value Value of hash table entry
* \param[in,out] user_data XML node
*/
void
hash2metafield(gpointer key, gpointer value, gpointer user_data)
{
char *crm_name = NULL;
if (key == NULL || value == NULL) {
return;
}
/* Filter out cluster-generated attributes that contain a '#' or ':'
* (like fail-count and last-failure).
*/
for (crm_name = key; *crm_name; ++crm_name) {
if ((*crm_name == '#') || (*crm_name == ':')) {
return;
}
}
crm_name = crm_meta_name(key);
hash2field(crm_name, value, user_data);
free(crm_name);
}
// nvpair handling
/*!
* \brief Create an XML name/value pair
*
* \param[in,out] parent If not \c NULL, make new XML node a child of this one
* \param[in] id Set this as XML ID (or NULL to auto-generate)
* \param[in] name Name to use
* \param[in] value Value to use
*
* \return New XML object on success, \c NULL otherwise
*/
xmlNode *
crm_create_nvpair_xml(xmlNode *parent, const char *id, const char *name,
const char *value)
{
xmlNode *nvp;
/* id can be NULL so we auto-generate one, and name can be NULL if this
* will be used to delete a name/value pair by ID, but both can't be NULL
*/
CRM_CHECK(id || name, return NULL);
nvp = create_xml_node(parent, XML_CIB_TAG_NVPAIR);
CRM_CHECK(nvp, return NULL);
if (id) {
crm_xml_add(nvp, XML_ATTR_ID, id);
} else {
const char *parent_id = ID(parent);
crm_xml_set_id(nvp, "%s-%s",
(parent_id? parent_id : XML_CIB_TAG_NVPAIR), name);
}
crm_xml_add(nvp, XML_NVPAIR_ATTR_NAME, name);
crm_xml_add(nvp, XML_NVPAIR_ATTR_VALUE, value);
return nvp;
}
/*!
* \brief Add XML nvpair element based on hash table entry
*
* Suitable for \c g_hash_table_foreach(), this function takes a hash table key
* and value, with an XML node passed as the user data, and adds an \c nvpair
* XML element with the specified name and value.
*
* \param[in] key Key of hash table entry
* \param[in] value Value of hash table entry
* \param[in,out] user_data XML node
*/
void
hash2nvpair(gpointer key, gpointer value, gpointer user_data)
{
const char *name = key;
const char *s_value = value;
xmlNode *xml_node = user_data;
crm_create_nvpair_xml(xml_node, name, name, s_value);
crm_trace("dumped: name=%s value=%s", name, s_value);
}
/*!
* \brief Retrieve XML attributes as a hash table
*
* Given an XML element, this will look for any \ element child,
* creating a hash table of (newly allocated string) name/value pairs taken
* first from the attributes element's NAME=VALUE XML attributes, and then
* from any \ children of attributes.
*
* \param[in] XML node to parse
*
* \return Hash table with name/value pairs
* \note It is the caller's responsibility to free the result using
* \c g_hash_table_destroy().
*/
GHashTable *
xml2list(const xmlNode *parent)
{
xmlNode *child = NULL;
xmlAttrPtr pIter = NULL;
xmlNode *nvpair_list = NULL;
GHashTable *nvpair_hash = pcmk__strkey_table(free, free);
CRM_CHECK(parent != NULL, return nvpair_hash);
nvpair_list = find_xml_node(parent, XML_TAG_ATTRS, FALSE);
if (nvpair_list == NULL) {
crm_trace("No attributes in %s", crm_element_name(parent));
crm_log_xml_trace(parent, "No attributes for resource op");
}
crm_log_xml_trace(nvpair_list, "Unpacking");
for (pIter = pcmk__xe_first_attr(nvpair_list); pIter != NULL;
pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = pcmk__xml_attr_value(pIter);
crm_trace("Added %s=%s", p_name, p_value);
g_hash_table_insert(nvpair_hash, strdup(p_name), strdup(p_value));
}
for (child = pcmk__xml_first_child(nvpair_list); child != NULL;
child = pcmk__xml_next(child)) {
if (strcmp((const char *)child->name, XML_TAG_PARAM) == 0) {
const char *key = crm_element_value(child, XML_NVPAIR_ATTR_NAME);
const char *value = crm_element_value(child, XML_NVPAIR_ATTR_VALUE);
crm_trace("Added %s=%s", key, value);
if (key != NULL && value != NULL) {
g_hash_table_insert(nvpair_hash, strdup(key), strdup(value));
}
}
}
return nvpair_hash;
}
void
pcmk__xe_set_bool_attr(xmlNodePtr node, const char *name, bool value)
{
crm_xml_add(node, name, value ? XML_BOOLEAN_TRUE : XML_BOOLEAN_FALSE);
}
int
pcmk__xe_get_bool_attr(const xmlNode *node, const char *name, bool *value)
{
const char *xml_value = NULL;
int ret, rc;
if (node == NULL) {
return ENODATA;
} else if (name == NULL || value == NULL) {
return EINVAL;
}
xml_value = crm_element_value(node, name);
if (xml_value == NULL) {
return ENODATA;
}
rc = crm_str_to_boolean(xml_value, &ret);
if (rc == 1) {
*value = ret;
return pcmk_rc_ok;
} else {
- return pcmk_rc_unknown_format;
+ return pcmk_rc_bad_input;
}
}
bool
pcmk__xe_attr_is_true(const xmlNode *node, const char *name)
{
bool value = false;
int rc;
rc = pcmk__xe_get_bool_attr(node, name, &value);
return rc == pcmk_rc_ok && value == true;
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include
int
pcmk_scan_nvpair(const char *input, char **name, char **value)
{
return pcmk__scan_nvpair(input, name, value);
}
char *
pcmk_format_nvpair(const char *name, const char *value,
const char *units)
{
return pcmk__format_nvpair(name, value, units);
}
char *
pcmk_format_named_time(const char *name, time_t epoch_time)
{
char *now_s = pcmk__epoch2str(&epoch_time, 0);
char *result = crm_strdup_printf("%s=\"%s\"", name, pcmk__s(now_s, ""));
free(now_s);
return result;
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/patchset_display.c b/lib/common/patchset_display.c
index 47834be7ab..731d437171 100644
--- a/lib/common/patchset_display.c
+++ b/lib/common/patchset_display.c
@@ -1,519 +1,519 @@
/*
* Copyright 2004-2023 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 "crmcommon_private.h"
/*!
* \internal
* \brief Output an XML patchset header
*
* This function parses a header from an XML patchset (an \p XML_ATTR_DIFF
* element and its children).
*
* All header lines contain three integers separated by dots, of the form
* {0}.{1}.{2}:
* * \p {0}: \p XML_ATTR_GENERATION_ADMIN
* * \p {1}: \p XML_ATTR_GENERATION
* * \p {2}: \p XML_ATTR_NUMUPDATES
*
* Lines containing \p "---" describe removals and end with the patch format
* number. Lines containing \p "+++" describe additions and end with the patch
* digest.
*
* \param[in,out] out Output object
* \param[in] patchset XML patchset to output
*
* \return Standard Pacemaker return code
*
* \note This function produces output only for text-like formats.
*/
static int
xml_show_patchset_header(pcmk__output_t *out, const xmlNode *patchset)
{
int rc = pcmk_rc_no_output;
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
xml_patch_versions(patchset, add, del);
if ((add[0] != del[0]) || (add[1] != del[1]) || (add[2] != del[2])) {
const char *fmt = crm_element_value(patchset, "format");
const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST);
out->info(out, "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
rc = out->info(out, "Diff: +++ %d.%d.%d %s",
add[0], add[1], add[2], digest);
} else if ((add[0] != 0) || (add[1] != 0) || (add[2] != 0)) {
rc = out->info(out, "Local-only Change: %d.%d.%d",
add[0], add[1], add[2]);
}
return rc;
}
/*!
* \internal
* \brief Output a user-friendly form of XML additions or removals
*
* \param[in,out] out Output object
* \param[in] prefix String to prepend to every line of output
* \param[in] data XML node to output
* \param[in] depth Current indentation level
* \param[in] options Group of \p pcmk__xml_fmt_options flags
*
* \return Standard Pacemaker return code
*
* \note This function produces output only for text-like formats.
*/
static int
xml_show_patchset_v1_recursive(pcmk__output_t *out, const char *prefix,
const xmlNode *data, int depth, uint32_t options)
{
if (!xml_has_children(data)
|| (crm_element_value(data, XML_DIFF_MARKER) != NULL)) {
// Found a change; clear the pcmk__xml_fmt_diff_short option if set
options &= ~pcmk__xml_fmt_diff_short;
if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) {
prefix = PCMK__XML_PREFIX_CREATED;
} else { // pcmk_is_set(options, pcmk__xml_fmt_diff_minus)
prefix = PCMK__XML_PREFIX_DELETED;
}
}
if (pcmk_is_set(options, pcmk__xml_fmt_diff_short)) {
int rc = pcmk_rc_no_output;
// Keep looking for the actual change
for (const xmlNode *child = pcmk__xml_first_child(data); child != NULL;
child = pcmk__xml_next(child)) {
int temp_rc = xml_show_patchset_v1_recursive(out, prefix, child,
depth + 1, options);
rc = pcmk__output_select_rc(rc, temp_rc);
}
return rc;
}
return pcmk__xml_show(out, prefix, data, depth,
options
|pcmk__xml_fmt_open
|pcmk__xml_fmt_children
|pcmk__xml_fmt_close);
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset (format 1)
*
* This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] patchset XML patchset to output
* \param[in] options Group of \p pcmk__xml_fmt_options flags
*
* \return Standard Pacemaker return code
*
* \note This function produces output only for text-like formats.
*/
static int
xml_show_patchset_v1(pcmk__output_t *out, const xmlNode *patchset,
uint32_t options)
{
const xmlNode *removed = NULL;
const xmlNode *added = NULL;
const xmlNode *child = NULL;
bool is_first = true;
int rc = xml_show_patchset_header(out, patchset);
/* It's not clear whether "- " or "+ " ever does *not* get overridden by
* PCMK__XML_PREFIX_DELETED or PCMK__XML_PREFIX_CREATED in practice.
* However, v1 patchsets can only exist during rolling upgrades from
* Pacemaker 1.1.11, so not worth worrying about.
*/
removed = find_xml_node(patchset, "diff-removed", FALSE);
for (child = pcmk__xml_first_child(removed); child != NULL;
child = pcmk__xml_next(child)) {
int temp_rc = xml_show_patchset_v1_recursive(out, "- ", child, 0,
options
|pcmk__xml_fmt_diff_minus);
rc = pcmk__output_select_rc(rc, temp_rc);
if (is_first) {
is_first = false;
} else {
rc = pcmk__output_select_rc(rc, out->info(out, " --- "));
}
}
is_first = true;
added = find_xml_node(patchset, "diff-added", FALSE);
for (child = pcmk__xml_first_child(added); child != NULL;
child = pcmk__xml_next(child)) {
int temp_rc = xml_show_patchset_v1_recursive(out, "+ ", child, 0,
options
|pcmk__xml_fmt_diff_plus);
rc = pcmk__output_select_rc(rc, temp_rc);
if (is_first) {
is_first = false;
} else {
rc = pcmk__output_select_rc(rc, out->info(out, " +++ "));
}
}
return rc;
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset (format 2)
*
* This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] patchset XML patchset to output
*
* \return Standard Pacemaker return code
*
* \note This function produces output only for text-like formats.
*/
static int
xml_show_patchset_v2(pcmk__output_t *out, const xmlNode *patchset)
{
int rc = xml_show_patchset_header(out, patchset);
int temp_rc = pcmk_rc_no_output;
for (const xmlNode *change = pcmk__xml_first_child(patchset);
change != NULL; change = pcmk__xml_next(change)) {
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
if (op == NULL) {
continue;
}
if (strcmp(op, "create") == 0) {
char *prefix = crm_strdup_printf(PCMK__XML_PREFIX_CREATED " %s: ",
xpath);
temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
pcmk__xml_fmt_pretty|pcmk__xml_fmt_open);
rc = pcmk__output_select_rc(rc, temp_rc);
// Overwrite all except the first two characters with spaces
for (char *ch = prefix + 2; *ch != '\0'; ch++) {
*ch = ' ';
}
temp_rc = pcmk__xml_show(out, prefix, change->children, 0,
pcmk__xml_fmt_pretty
|pcmk__xml_fmt_children
|pcmk__xml_fmt_close);
rc = pcmk__output_select_rc(rc, temp_rc);
free(prefix);
} else if (strcmp(op, "move") == 0) {
const char *position = crm_element_value(change, XML_DIFF_POSITION);
temp_rc = out->info(out,
PCMK__XML_PREFIX_MOVED " %s moved to offset %s",
xpath, position);
rc = pcmk__output_select_rc(rc, temp_rc);
} else if (strcmp(op, "modify") == 0) {
xmlNode *clist = first_named_child(change, XML_DIFF_LIST);
GString *buffer_set = NULL;
GString *buffer_unset = NULL;
for (const xmlNode *child = pcmk__xml_first_child(clist);
child != NULL; child = pcmk__xml_next(child)) {
const char *name = crm_element_value(child, "name");
op = crm_element_value(child, XML_DIFF_OP);
if (op == NULL) {
continue;
}
if (strcmp(op, "set") == 0) {
const char *value = crm_element_value(child, "value");
pcmk__add_separated_word(&buffer_set, 256, "@", ", ");
pcmk__g_strcat(buffer_set, name, "=", value, NULL);
} else if (strcmp(op, "unset") == 0) {
pcmk__add_separated_word(&buffer_unset, 256, "@", ", ");
g_string_append(buffer_unset, name);
}
}
if (buffer_set != NULL) {
temp_rc = out->info(out, "+ %s: %s", xpath, buffer_set->str);
rc = pcmk__output_select_rc(rc, temp_rc);
g_string_free(buffer_set, TRUE);
}
if (buffer_unset != NULL) {
temp_rc = out->info(out, "-- %s: %s",
xpath, buffer_unset->str);
rc = pcmk__output_select_rc(rc, temp_rc);
g_string_free(buffer_unset, TRUE);
}
} else if (strcmp(op, "delete") == 0) {
int position = -1;
crm_element_value_int(change, XML_DIFF_POSITION, &position);
if (position >= 0) {
temp_rc = out->info(out, "-- %s (%d)", xpath, position);
} else {
temp_rc = out->info(out, "-- %s", xpath);
}
rc = pcmk__output_select_rc(rc, temp_rc);
}
}
return rc;
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset
*
* This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain only the XML patchset
*/
PCMK__OUTPUT_ARGS("xml-patchset", "xmlNodePtr")
static int
xml_patchset_default(pcmk__output_t *out, va_list args)
{
xmlNodePtr patchset = va_arg(args, xmlNodePtr);
int format = 1;
if (patchset == NULL) {
crm_trace("Empty patch");
return pcmk_rc_no_output;
}
crm_element_value_int(patchset, "format", &format);
switch (format) {
case 1:
return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
case 2:
return xml_show_patchset_v2(out, patchset);
default:
crm_err("Unknown patch format: %d", format);
- return pcmk_rc_unknown_format;
+ return pcmk_rc_bad_xml_patch;
}
}
/*!
* \internal
* \brief Output a user-friendly form of an XML patchset
*
* This function parses an XML patchset (an \p XML_ATTR_DIFF element and its
* children) into a user-friendly combined diff output.
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain only the XML patchset
*/
PCMK__OUTPUT_ARGS("xml-patchset", "xmlNodePtr")
static int
xml_patchset_log(pcmk__output_t *out, va_list args)
{
static struct qb_log_callsite *patchset_cs = NULL;
xmlNodePtr patchset = va_arg(args, xmlNodePtr);
uint8_t log_level = pcmk__output_get_log_level(out);
int format = 1;
if (log_level == LOG_NEVER) {
return pcmk_rc_no_output;
}
if (patchset == NULL) {
crm_trace("Empty patch");
return pcmk_rc_no_output;
}
if (patchset_cs == NULL) {
patchset_cs = qb_log_callsite_get(__func__, __FILE__, "xml-patchset",
log_level, __LINE__,
crm_trace_nonlog);
}
if (!crm_is_callsite_active(patchset_cs, log_level, crm_trace_nonlog)) {
// Nothing would be logged, so skip all the work
return pcmk_rc_no_output;
}
crm_element_value_int(patchset, "format", &format);
switch (format) {
case 1:
if (log_level < LOG_DEBUG) {
return xml_show_patchset_v1(out, patchset,
pcmk__xml_fmt_pretty
|pcmk__xml_fmt_diff_short);
}
return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
case 2:
return xml_show_patchset_v2(out, patchset);
default:
crm_err("Unknown patch format: %d", format);
- return pcmk_rc_unknown_format;
+ return pcmk_rc_bad_xml_patch;
}
}
/*!
* \internal
* \brief Output an XML patchset
*
* This function outputs an XML patchset (an \p XML_ATTR_DIFF element and its
* children) without modification, as a CDATA block.
*
* \param[in,out] out Output object
* \param[in] args Message-specific arguments
*
* \return Standard Pacemaker return code
*
* \note \p args should contain only the XML patchset
*/
PCMK__OUTPUT_ARGS("xml-patchset", "xmlNodePtr")
static int
xml_patchset_xml(pcmk__output_t *out, va_list args)
{
xmlNodePtr patchset = va_arg(args, xmlNodePtr);
if (patchset != NULL) {
char *buf = dump_xml_formatted_with_text(patchset);
out->output_xml(out, "xml-patchset", buf);
free(buf);
return pcmk_rc_ok;
}
crm_trace("Empty patch");
return pcmk_rc_no_output;
}
static pcmk__message_entry_t fmt_functions[] = {
{ "xml-patchset", "default", xml_patchset_default },
{ "xml-patchset", "log", xml_patchset_log },
{ "xml-patchset", "xml", xml_patchset_xml },
{ NULL, NULL, NULL }
};
/*!
* \internal
* \brief Register the formatting functions for XML patchsets
*
* \param[in,out] out Output object
*/
void
pcmk__register_patchset_messages(pcmk__output_t *out) {
pcmk__register_messages(out, fmt_functions);
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include
void
xml_log_patchset(uint8_t log_level, const char *function,
const xmlNode *patchset)
{
/* This function has some duplication relative to the message functions.
* This way, we can maintain the const xmlNode * in the signature. The
* message functions must be non-const. They have to support XML output
* objects, which must make a copy of a the patchset, requiring a non-const
* function call.
*
* In contrast, this legacy function doesn't need to support XML output.
*/
static struct qb_log_callsite *patchset_cs = NULL;
pcmk__output_t *out = NULL;
int format = 1;
int rc = pcmk_rc_no_output;
switch (log_level) {
case LOG_NEVER:
return;
case LOG_STDOUT:
CRM_CHECK(pcmk__text_output_new(&out, NULL) == pcmk_rc_ok, return);
break;
default:
if (patchset_cs == NULL) {
patchset_cs = qb_log_callsite_get(__func__, __FILE__,
"xml-patchset", log_level,
__LINE__, crm_trace_nonlog);
}
if (!crm_is_callsite_active(patchset_cs, log_level,
crm_trace_nonlog)) {
return;
}
CRM_CHECK(pcmk__log_output_new(&out) == pcmk_rc_ok, return);
pcmk__output_set_log_level(out, log_level);
break;
}
if (patchset == NULL) {
// Should come after the LOG_NEVER check
crm_trace("Empty patch");
goto done;
}
crm_element_value_int(patchset, "format", &format);
switch (format) {
case 1:
if (log_level < LOG_DEBUG) {
rc = xml_show_patchset_v1(out, patchset,
pcmk__xml_fmt_pretty
|pcmk__xml_fmt_diff_short);
} else { // Note: LOG_STDOUT > LOG_DEBUG
rc = xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty);
}
break;
case 2:
rc = xml_show_patchset_v2(out, patchset);
break;
default:
crm_err("Unknown patch format: %d", format);
- rc = pcmk_rc_unknown_format;
+ rc = pcmk_rc_bad_xml_patch;
break;
}
done:
out->finish(out, pcmk_rc2exitc(rc), true, NULL);
pcmk__output_free(out);
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/pid.c b/lib/common/pid.c
index b9749c6b76..bb53153dcf 100644
--- a/lib/common/pid.c
+++ b/lib/common/pid.c
@@ -1,240 +1,247 @@
/*
* Copyright 2004-2022 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include
#include
#include
#include
int
pcmk__pid_active(pid_t pid, const char *daemon)
{
static pid_t last_asked_pid = 0; /* log spam prevention */
int rc = 0;
if (pid <= 0) {
return EINVAL;
}
rc = kill(pid, 0);
if ((rc < 0) && (errno == ESRCH)) {
return ESRCH; /* no such PID detected */
} else if ((daemon == NULL) || !pcmk__procfs_has_pids()) {
// The kill result is all we have, we can't check the name
if (rc == 0) {
return pcmk_rc_ok;
}
rc = errno;
if (last_asked_pid != pid) {
crm_info("Cannot examine PID %lld: %s",
(long long) pid, pcmk_rc_str(rc));
last_asked_pid = pid;
}
return rc; /* errno != ESRCH */
} else {
/* make sure PID hasn't been reused by another process
XXX: might still be just a zombie, which could confuse decisions */
bool checked_through_kill = (rc == 0);
char exe_path[PATH_MAX], myexe_path[PATH_MAX];
rc = pcmk__procfs_pid2path(pid, exe_path, sizeof(exe_path));
if (rc != pcmk_rc_ok) {
if (rc != EACCES) {
// Check again to filter out races
if ((kill(pid, 0) < 0) && (errno == ESRCH)) {
return ESRCH;
}
}
if (last_asked_pid != pid) {
if (rc == EACCES) {
crm_info("Could not get executable for PID %lld: %s "
CRM_XS " rc=%d",
(long long) pid, pcmk_rc_str(rc), rc);
} else {
crm_err("Could not get executable for PID %lld: %s "
CRM_XS " rc=%d",
(long long) pid, pcmk_rc_str(rc), rc);
}
last_asked_pid = pid;
}
if (rc == EACCES) {
// Trust kill if it was OK (we can't double-check via path)
return checked_through_kill? pcmk_rc_ok : EACCES;
} else {
return ESRCH; /* most likely errno == ENOENT */
}
}
if (daemon[0] != '/') {
rc = snprintf(myexe_path, sizeof(myexe_path), CRM_DAEMON_DIR"/%s",
daemon);
} else {
rc = snprintf(myexe_path, sizeof(myexe_path), "%s", daemon);
}
if (rc > 0 && rc < sizeof(myexe_path) && !strcmp(exe_path, myexe_path)) {
return pcmk_rc_ok;
}
}
return ESRCH;
}
#define LOCKSTRLEN 11
/*!
* \internal
* \brief Read a process ID from a file
*
* \param[in] filename Process ID file to read
* \param[out] pid Where to put PID that was read
*
* \return Standard Pacemaker return code
*/
int
pcmk__read_pidfile(const char *filename, pid_t *pid)
{
int fd;
struct stat sbuf;
- int rc = pcmk_rc_unknown_format;
+ int rc = pcmk_rc_ok;
long long pid_read = 0;
char buf[LOCKSTRLEN + 1];
CRM_CHECK((filename != NULL) && (pid != NULL), return EINVAL);
fd = open(filename, O_RDONLY);
if (fd < 0) {
return errno;
}
if ((fstat(fd, &sbuf) >= 0) && (sbuf.st_size < LOCKSTRLEN)) {
sleep(2); /* if someone was about to create one,
* give'm a sec to do so
*/
}
if (read(fd, buf, sizeof(buf)) < 1) {
rc = errno;
goto bail;
}
- if (sscanf(buf, "%lld", &pid_read) > 0) {
+ errno = 0;
+ rc = sscanf(buf, "%lld", &pid_read);
+
+ if (rc > 0) {
if (pid_read <= 0) {
rc = ESRCH;
} else {
rc = pcmk_rc_ok;
*pid = (pid_t) pid_read;
crm_trace("Read pid %lld from %s", pid_read, filename);
}
+ } else if (rc == 0) {
+ rc = ENODATA;
+ } else {
+ rc = errno;
}
bail:
close(fd);
return rc;
}
/*!
* \internal
* \brief Check whether a process from a PID file matches expected values
*
* \param[in] filename Path of PID file
* \param[in] expected_pid If positive, compare to this PID
* \param[in] expected_name If not NULL, the PID from the PID file is valid
* only if it is active as a process with this name
* \param[out] pid If not NULL, store PID found in PID file here
*
* \return Standard Pacemaker return code
*/
int
pcmk__pidfile_matches(const char *filename, pid_t expected_pid,
const char *expected_name, pid_t *pid)
{
pid_t pidfile_pid = 0;
int rc = pcmk__read_pidfile(filename, &pidfile_pid);
if (pid) {
*pid = pidfile_pid;
}
if (rc != pcmk_rc_ok) {
// Error reading PID file or invalid contents
unlink(filename);
rc = ENOENT;
} else if ((expected_pid > 0) && (pidfile_pid == expected_pid)) {
// PID in file matches what was expected
rc = pcmk_rc_ok;
} else if (pcmk__pid_active(pidfile_pid, expected_name) == ESRCH) {
// Contains a stale value
unlink(filename);
rc = ENOENT;
} else if ((expected_pid > 0) && (pidfile_pid != expected_pid)) {
// Locked by existing process
rc = EEXIST;
}
return rc;
}
/*!
* \internal
* \brief Create a PID file for the current process (if not already existent)
*
* \param[in] filename Name of PID file to create
* \param[in] name Name of current process
*
* \return Standard Pacemaker return code
*/
int
pcmk__lock_pidfile(const char *filename, const char *name)
{
pid_t mypid = getpid();
int fd = 0;
int rc = 0;
char buf[LOCKSTRLEN + 2];
rc = pcmk__pidfile_matches(filename, 0, name, NULL);
if ((rc != pcmk_rc_ok) && (rc != ENOENT)) {
// Locked by existing process
return rc;
}
fd = open(filename, O_CREAT | O_WRONLY | O_EXCL, 0644);
if (fd < 0) {
return errno;
}
snprintf(buf, sizeof(buf), "%*lld\n", LOCKSTRLEN - 1, (long long) mypid);
rc = write(fd, buf, LOCKSTRLEN);
close(fd);
if (rc != LOCKSTRLEN) {
crm_perror(LOG_ERR, "Incomplete write to %s", filename);
return errno;
}
rc = pcmk__pidfile_matches(filename, mypid, name, NULL);
if (rc != pcmk_rc_ok) {
// Something is really wrong -- maybe I/O error on read back?
unlink(filename);
}
return rc;
}
diff --git a/lib/common/results.c b/lib/common/results.c
index 001d1f5fd7..93d79eb3be 100644
--- a/lib/common/results.c
+++ b/lib/common/results.c
@@ -1,1037 +1,1049 @@
/*
* Copyright 2004-2023 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
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#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)
{
CRM_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;
}
// @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,
+ },
};
/*!
* \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_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_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_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:
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_unknown_format:
return CRM_EX_NOSUCH;
case pcmk_rc_node_unknown:
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;
+
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
const char *
bz2_strerror(int rc)
{
// See ftp://sources.redhat.com/pub/bzip2/docs/manual_3.html#SEC17
switch (rc) {
case BZ_OK:
case BZ_RUN_OK:
case BZ_FLUSH_OK:
case BZ_FINISH_OK:
case BZ_STREAM_END:
return "Ok";
case BZ_CONFIG_ERROR:
return "libbz2 has been improperly compiled on your platform";
case BZ_SEQUENCE_ERROR:
return "library functions called in the wrong order";
case BZ_PARAM_ERROR:
return "parameter is out of range or otherwise incorrect";
case BZ_MEM_ERROR:
return "memory allocation failed";
case BZ_DATA_ERROR:
return "data integrity error is detected during decompression";
case BZ_DATA_ERROR_MAGIC:
return "the compressed stream does not start with the correct magic bytes";
case BZ_IO_ERROR:
return "error reading or writing in the compressed file";
case BZ_UNEXPECTED_EOF:
return "compressed file finishes before the logical end of stream is detected";
case BZ_OUTBUFF_FULL:
return "output data will not fit into the buffer provided";
}
return "Data compression error";
}
crm_exit_t
crm_exit(crm_exit_t rc)
{
/* 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) rc) < 0) || (((int) rc) > CRM_EX_MAX)) {
rc = CRM_EX_ERROR;
}
mainloop_cleanup();
crm_xml_cleanup();
free(pcmk__our_nodename);
if (crm_system_name) {
crm_info("Exiting %s " CRM_XS " with status %d", crm_system_name, rc);
free(crm_system_name);
} else {
crm_trace("Exiting with status %d", rc);
}
pcmk__free_common_logger();
qb_log_fini(); // Don't log anything after this point
exit(rc);
}
/*
* 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);
CRM_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;
pcmk__str_update(&dst->exit_reason, src->exit_reason);
pcmk__str_update(&dst->action_stdout, src->action_stdout);
pcmk__str_update(&dst->action_stderr, src->action_stderr);
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include
crm_exit_t
crm_errno2exit(int rc)
{
return pcmk_rc2exitc(pcmk_legacy2rc(rc));
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/strings.c b/lib/common/strings.c
index 19dced47f7..b245102c3f 100644
--- a/lib/common/strings.c
+++ b/lib/common/strings.c
@@ -1,1357 +1,1363 @@
/*
* Copyright 2004-2023 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
+#include "crm/common/results.h"
#include
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include
#include
#include
#include
#include
#include // DBL_MIN
#include
#include
#include
/*!
* \internal
* \brief Scan a long long integer from a string
*
* \param[in] text String to scan
* \param[out] result If not NULL, where to store scanned value
* \param[in] default_value Value to use if text is NULL or invalid
* \param[out] end_text If not NULL, where to store pointer to first
* non-integer character
*
* \return Standard Pacemaker return code (\c pcmk_rc_ok on success,
* \c EINVAL on failed string conversion due to invalid input,
* or \c EOVERFLOW on arithmetic overflow)
* \note Sets \c errno on error
*/
static int
scan_ll(const char *text, long long *result, long long default_value,
char **end_text)
{
long long local_result = default_value;
char *local_end_text = NULL;
int rc = pcmk_rc_ok;
errno = 0;
if (text != NULL) {
local_result = strtoll(text, &local_end_text, 10);
if (errno == ERANGE) {
rc = EOVERFLOW;
crm_warn("Integer parsed from '%s' was clipped to %lld",
text, local_result);
} else if (errno != 0) {
rc = errno;
local_result = default_value;
crm_warn("Could not parse integer from '%s' (using %lld instead): "
"%s", text, default_value, pcmk_rc_str(rc));
} else if (local_end_text == text) {
rc = EINVAL;
local_result = default_value;
crm_warn("Could not parse integer from '%s' (using %lld instead): "
"No digits found", text, default_value);
}
if ((end_text == NULL) && !pcmk__str_empty(local_end_text)) {
crm_warn("Characters left over after parsing '%s': '%s'",
text, local_end_text);
}
errno = rc;
}
if (end_text != NULL) {
*end_text = local_end_text;
}
if (result != NULL) {
*result = local_result;
}
return rc;
}
/*!
* \internal
* \brief Scan a long long integer value from a string
*
* \param[in] text The string to scan (may be NULL)
* \param[out] result Where to store result (or NULL to ignore)
* \param[in] default_value Value to use if text is NULL or invalid
*
* \return Standard Pacemaker return code
*/
int
pcmk__scan_ll(const char *text, long long *result, long long default_value)
{
long long local_result = default_value;
int rc = pcmk_rc_ok;
if (text != NULL) {
rc = scan_ll(text, &local_result, default_value, NULL);
if (rc != pcmk_rc_ok) {
local_result = default_value;
}
}
if (result != NULL) {
*result = local_result;
}
return rc;
}
/*!
* \internal
* \brief Scan an integer value from a string, constrained to a minimum
*
* \param[in] text The string to scan (may be NULL)
* \param[out] result Where to store result (or NULL to ignore)
* \param[in] minimum Value to use as default and minimum
*
* \return Standard Pacemaker return code
* \note If the value is larger than the maximum integer, EOVERFLOW will be
* returned and \p result will be set to the maximum integer.
*/
int
pcmk__scan_min_int(const char *text, int *result, int minimum)
{
int rc;
long long result_ll;
rc = pcmk__scan_ll(text, &result_ll, (long long) minimum);
if (result_ll < (long long) minimum) {
crm_warn("Clipped '%s' to minimum acceptable value %d", text, minimum);
result_ll = (long long) minimum;
} else if (result_ll > INT_MAX) {
crm_warn("Clipped '%s' to maximum integer %d", text, INT_MAX);
result_ll = (long long) INT_MAX;
rc = EOVERFLOW;
}
if (result != NULL) {
*result = (int) result_ll;
}
return rc;
}
/*!
* \internal
* \brief Scan a TCP port number from a string
*
* \param[in] text The string to scan
* \param[out] port Where to store result (or NULL to ignore)
*
* \return Standard Pacemaker return code
* \note \p port will be -1 if \p text is NULL or invalid
*/
int
pcmk__scan_port(const char *text, int *port)
{
long long port_ll;
int rc = pcmk__scan_ll(text, &port_ll, -1LL);
if ((text != NULL) && (rc == pcmk_rc_ok) // wasn't default or invalid
&& ((port_ll < 0LL) || (port_ll > 65535LL))) {
crm_warn("Ignoring port specification '%s' "
"not in valid range (0-65535)", text);
rc = (port_ll < 0LL)? pcmk_rc_before_range : pcmk_rc_after_range;
port_ll = -1LL;
}
if (port != NULL) {
*port = (int) port_ll;
}
return rc;
}
/*!
* \internal
* \brief Scan a double-precision floating-point value from a string
*
* \param[in] text The string to parse
* \param[out] result Parsed value on success, or
* \c PCMK__PARSE_DBL_DEFAULT on error
* \param[in] default_text Default string to parse if \p text is
* \c NULL
* \param[out] end_text If not \c NULL, where to store a pointer
* to the position immediately after the
* value
*
* \return Standard Pacemaker return code (\c pcmk_rc_ok on success,
* \c EINVAL on failed string conversion due to invalid input,
* \c EOVERFLOW on arithmetic overflow, \c pcmk_rc_underflow
* on arithmetic underflow, or \c errno from \c strtod() on
* other parse errors)
*/
int
pcmk__scan_double(const char *text, double *result, const char *default_text,
char **end_text)
{
int rc = pcmk_rc_ok;
char *local_end_text = NULL;
CRM_ASSERT(result != NULL);
*result = PCMK__PARSE_DBL_DEFAULT;
text = (text != NULL) ? text : default_text;
if (text == NULL) {
rc = EINVAL;
crm_debug("No text and no default conversion value supplied");
} else {
errno = 0;
*result = strtod(text, &local_end_text);
if (errno == ERANGE) {
/*
* Overflow: strtod() returns +/- HUGE_VAL and sets errno to
* ERANGE
*
* Underflow: strtod() returns "a value whose magnitude is
* no greater than the smallest normalized
* positive" double. Whether ERANGE is set is
* implementation-defined.
*/
const char *over_under;
if (QB_ABS(*result) > DBL_MIN) {
rc = EOVERFLOW;
over_under = "over";
} else {
rc = pcmk_rc_underflow;
over_under = "under";
}
crm_debug("Floating-point value parsed from '%s' would %sflow "
"(using %g instead)", text, over_under, *result);
} else if (errno != 0) {
rc = errno;
// strtod() set *result = 0 on parse failure
*result = PCMK__PARSE_DBL_DEFAULT;
crm_debug("Could not parse floating-point value from '%s' (using "
"%.1f instead): %s", text, PCMK__PARSE_DBL_DEFAULT,
pcmk_rc_str(rc));
} else if (local_end_text == text) {
// errno == 0, but nothing was parsed
rc = EINVAL;
*result = PCMK__PARSE_DBL_DEFAULT;
crm_debug("Could not parse floating-point value from '%s' (using "
"%.1f instead): No digits found", text,
PCMK__PARSE_DBL_DEFAULT);
} else if (QB_ABS(*result) <= DBL_MIN) {
/*
* errno == 0 and text was parsed, but value might have
* underflowed.
*
* ERANGE might not be set for underflow. Check magnitude
* of *result, but also make sure the input number is not
* actually zero (0 <= DBL_MIN is not underflow).
*
* This check must come last. A parse failure in strtod()
* also sets *result == 0, so a parse failure would match
* this test condition prematurely.
*/
for (const char *p = text; p != local_end_text; p++) {
if (strchr("0.eE", *p) == NULL) {
rc = pcmk_rc_underflow;
crm_debug("Floating-point value parsed from '%s' would "
"underflow (using %g instead)", text, *result);
break;
}
}
} else {
crm_trace("Floating-point value parsed successfully from "
"'%s': %g", text, *result);
}
if ((end_text == NULL) && !pcmk__str_empty(local_end_text)) {
crm_debug("Characters left over after parsing '%s': '%s'",
text, local_end_text);
}
}
if (end_text != NULL) {
*end_text = local_end_text;
}
return rc;
}
/*!
* \internal
* \brief Parse a guint from a string stored in a hash table
*
* \param[in] table Hash table to search
* \param[in] key Hash table key to use to retrieve string
* \param[in] default_val What to use if key has no entry in table
* \param[out] result If not NULL, where to store parsed integer
*
* \return Standard Pacemaker return code
*/
int
pcmk__guint_from_hash(GHashTable *table, const char *key, guint default_val,
guint *result)
{
const char *value;
long long value_ll;
int rc = pcmk_rc_ok;
CRM_CHECK((table != NULL) && (key != NULL), return EINVAL);
if (result != NULL) {
*result = default_val;
}
value = g_hash_table_lookup(table, key);
if (value == NULL) {
return pcmk_rc_ok;
}
rc = pcmk__scan_ll(value, &value_ll, 0LL);
if (rc != pcmk_rc_ok) {
return rc;
}
if ((value_ll < 0) || (value_ll > G_MAXUINT)) {
crm_warn("Could not parse non-negative integer from %s", value);
return ERANGE;
}
if (result != NULL) {
*result = (guint) value_ll;
}
return pcmk_rc_ok;
}
#ifndef NUMCHARS
# define NUMCHARS "0123456789."
#endif
#ifndef WHITESPACE
# define WHITESPACE " \t\n\r\f"
#endif
/*!
* \brief Parse a time+units string and return milliseconds equivalent
*
* \param[in] input String with a number and optional unit (optionally
* with whitespace before and/or after the number). If
* missing, the unit defaults to seconds.
*
* \return Milliseconds corresponding to string expression, or
* PCMK__PARSE_INT_DEFAULT on error
*/
long long
crm_get_msec(const char *input)
{
const char *num_start = NULL;
const char *units;
long long multiplier = 1000;
long long divisor = 1;
long long msec = PCMK__PARSE_INT_DEFAULT;
size_t num_len = 0;
char *end_text = NULL;
if (input == NULL) {
return PCMK__PARSE_INT_DEFAULT;
}
num_start = input + strspn(input, WHITESPACE);
num_len = strspn(num_start, NUMCHARS);
if (num_len < 1) {
return PCMK__PARSE_INT_DEFAULT;
}
units = num_start + num_len;
units += strspn(units, WHITESPACE);
if (!strncasecmp(units, "ms", 2) || !strncasecmp(units, "msec", 4)) {
multiplier = 1;
divisor = 1;
} else if (!strncasecmp(units, "us", 2) || !strncasecmp(units, "usec", 4)) {
multiplier = 1;
divisor = 1000;
} else if (!strncasecmp(units, "s", 1) || !strncasecmp(units, "sec", 3)) {
multiplier = 1000;
divisor = 1;
} else if (!strncasecmp(units, "m", 1) || !strncasecmp(units, "min", 3)) {
multiplier = 60 * 1000;
divisor = 1;
} else if (!strncasecmp(units, "h", 1) || !strncasecmp(units, "hr", 2)) {
multiplier = 60 * 60 * 1000;
divisor = 1;
} else if ((*units != '\0') && (*units != '\n') && (*units != '\r')) {
return PCMK__PARSE_INT_DEFAULT;
}
scan_ll(num_start, &msec, PCMK__PARSE_INT_DEFAULT, &end_text);
if (msec > (LLONG_MAX / multiplier)) {
// Arithmetics overflow while multiplier/divisor mutually exclusive
return LLONG_MAX;
}
msec *= multiplier;
msec /= divisor;
return msec;
}
gboolean
crm_is_true(const char *s)
{
gboolean ret = FALSE;
if (s != NULL) {
crm_str_to_boolean(s, &ret);
}
return ret;
}
int
crm_str_to_boolean(const char *s, int *ret)
{
if (s == NULL) {
return -1;
} else if (strcasecmp(s, "true") == 0
|| strcasecmp(s, "on") == 0
|| strcasecmp(s, "yes") == 0 || strcasecmp(s, "y") == 0 || strcasecmp(s, "1") == 0) {
*ret = TRUE;
return 1;
} else if (strcasecmp(s, "false") == 0
|| strcasecmp(s, "off") == 0
|| strcasecmp(s, "no") == 0 || strcasecmp(s, "n") == 0 || strcasecmp(s, "0") == 0) {
*ret = FALSE;
return 1;
}
return -1;
}
/*!
* \internal
* \brief Replace any trailing newlines in a string with \0's
*
* \param[in,out] str String to trim
*
* \return \p str
*/
char *
pcmk__trim(char *str)
{
int len;
if (str == NULL) {
return str;
}
for (len = strlen(str) - 1; len >= 0 && str[len] == '\n'; len--) {
str[len] = '\0';
}
return str;
}
/*!
* \brief Check whether a string starts with a certain sequence
*
* \param[in] str String to check
* \param[in] prefix Sequence to match against beginning of \p str
*
* \return \c true if \p str begins with match, \c false otherwise
* \note This is equivalent to !strncmp(s, prefix, strlen(prefix))
* but is likely less efficient when prefix is a string literal
* if the compiler optimizes away the strlen() at compile time,
* and more efficient otherwise.
*/
bool
pcmk__starts_with(const char *str, const char *prefix)
{
const char *s = str;
const char *p = prefix;
if (!s || !p) {
return false;
}
while (*s && *p) {
if (*s++ != *p++) {
return false;
}
}
return (*p == 0);
}
static inline bool
ends_with(const char *s, const char *match, bool as_extension)
{
if (pcmk__str_empty(match)) {
return true;
} else if (s == NULL) {
return false;
} else {
size_t slen, mlen;
/* Besides as_extension, we could also check
!strchr(&match[1], match[0]) but that would be inefficient.
*/
if (as_extension) {
s = strrchr(s, match[0]);
return (s == NULL)? false : !strcmp(s, match);
}
mlen = strlen(match);
slen = strlen(s);
return ((slen >= mlen) && !strcmp(s + slen - mlen, match));
}
}
/*!
* \internal
* \brief Check whether a string ends with a certain sequence
*
* \param[in] s String to check
* \param[in] match Sequence to match against end of \p s
*
* \return \c true if \p s ends case-sensitively with match, \c false otherwise
* \note pcmk__ends_with_ext() can be used if the first character of match
* does not recur in match.
*/
bool
pcmk__ends_with(const char *s, const char *match)
{
return ends_with(s, match, false);
}
/*!
* \internal
* \brief Check whether a string ends with a certain "extension"
*
* \param[in] s String to check
* \param[in] match Extension to match against end of \p s, that is,
* its first character must not occur anywhere
* in the rest of that very sequence (example: file
* extension where the last dot is its delimiter,
* e.g., ".html"); incorrect results may be
* returned otherwise.
*
* \return \c true if \p s ends (verbatim, i.e., case sensitively)
* with "extension" designated as \p match (including empty
* string), \c false otherwise
*
* \note Main incentive to prefer this function over \c pcmk__ends_with()
* where possible is the efficiency (at the cost of added
* restriction on \p match as stated; the complexity class
* remains the same, though: BigO(M+N) vs. BigO(M+2N)).
*/
bool
pcmk__ends_with_ext(const char *s, const char *match)
{
return ends_with(s, match, true);
}
/*!
* \internal
* \brief Create a hash of a string suitable for use with GHashTable
*
* \param[in] v String to hash
*
* \return A hash of \p v compatible with g_str_hash() before glib 2.28
* \note glib changed their hash implementation:
*
* https://gitlab.gnome.org/GNOME/glib/commit/354d655ba8a54b754cb5a3efb42767327775696c
*
* Note that the new g_str_hash is presumably a *better* hash (it's actually
* a correct implementation of DJB's hash), but we need to preserve existing
* behaviour, because the hash key ultimately determines the "sort" order
* when iterating through GHashTables, which affects allocation of scores to
* clone instances when iterating through rsc->allowed_nodes. It (somehow)
* also appears to have some minor impact on the ordering of a few
* pseudo_event IDs in the transition graph.
*/
static guint
pcmk__str_hash(gconstpointer v)
{
const signed char *p;
guint32 h = 0;
for (p = v; *p != '\0'; p++)
h = (h << 5) - h + *p;
return h;
}
/*!
* \internal
* \brief Create a hash table with case-sensitive strings as keys
*
* \param[in] key_destroy_func Function to free a key
* \param[in] value_destroy_func Function to free a value
*
* \return Newly allocated hash table
* \note It is the caller's responsibility to free the result, using
* g_hash_table_destroy().
*/
GHashTable *
pcmk__strkey_table(GDestroyNotify key_destroy_func,
GDestroyNotify value_destroy_func)
{
return g_hash_table_new_full(pcmk__str_hash, g_str_equal,
key_destroy_func, value_destroy_func);
}
/* used with hash tables where case does not matter */
static gboolean
pcmk__strcase_equal(gconstpointer a, gconstpointer b)
{
return pcmk__str_eq((const char *)a, (const char *)b, pcmk__str_casei);
}
static guint
pcmk__strcase_hash(gconstpointer v)
{
const signed char *p;
guint32 h = 0;
for (p = v; *p != '\0'; p++)
h = (h << 5) - h + g_ascii_tolower(*p);
return h;
}
/*!
* \internal
* \brief Create a hash table with case-insensitive strings as keys
*
* \param[in] key_destroy_func Function to free a key
* \param[in] value_destroy_func Function to free a value
*
* \return Newly allocated hash table
* \note It is the caller's responsibility to free the result, using
* g_hash_table_destroy().
*/
GHashTable *
pcmk__strikey_table(GDestroyNotify key_destroy_func,
GDestroyNotify value_destroy_func)
{
return g_hash_table_new_full(pcmk__strcase_hash, pcmk__strcase_equal,
key_destroy_func, value_destroy_func);
}
static void
copy_str_table_entry(gpointer key, gpointer value, gpointer user_data)
{
if (key && value && user_data) {
g_hash_table_insert((GHashTable*)user_data, strdup(key), strdup(value));
}
}
/*!
* \internal
* \brief Copy a hash table that uses dynamically allocated strings
*
* \param[in,out] old_table Hash table to duplicate
*
* \return New hash table with copies of everything in \p old_table
* \note This assumes the hash table uses dynamically allocated strings -- that
* is, both the key and value free functions are free().
*/
GHashTable *
pcmk__str_table_dup(GHashTable *old_table)
{
GHashTable *new_table = NULL;
if (old_table) {
new_table = pcmk__strkey_table(free, free);
g_hash_table_foreach(old_table, copy_str_table_entry, new_table);
}
return new_table;
}
/*!
* \internal
* \brief Add a word to a string list of words
*
* \param[in,out] list Pointer to current string list (may not be \p NULL)
* \param[in] init_size \p list will be initialized to at least this size,
* if it needs initialization (if 0, use GLib's default
* initial string size)
* \param[in] word String to add to \p list (\p list will be
* unchanged if this is \p NULL or the empty string)
* \param[in] separator String to separate words in \p list
* (a space will be used if this is NULL)
*
* \note \p word may contain \p separator, though that would be a bad idea if
* the string needs to be parsed later.
*/
void
pcmk__add_separated_word(GString **list, size_t init_size, const char *word,
const char *separator)
{
CRM_ASSERT(list != NULL);
if (pcmk__str_empty(word)) {
return;
}
if (*list == NULL) {
if (init_size > 0) {
*list = g_string_sized_new(init_size);
} else {
*list = g_string_new(NULL);
}
}
if ((*list)->len == 0) {
// Don't add a separator before the first word in the list
separator = "";
} else if (separator == NULL) {
// Default to space-separated
separator = " ";
}
g_string_append(*list, separator);
g_string_append(*list, word);
}
/*!
* \internal
* \brief Compress data
*
* \param[in] data Data to compress
* \param[in] length Number of characters of data to compress
* \param[in] max Maximum size of compressed data (or 0 to estimate)
* \param[out] result Where to store newly allocated compressed result
* \param[out] result_len Where to store actual compressed length of result
*
* \return Standard Pacemaker return code
*/
int
pcmk__compress(const char *data, unsigned int length, unsigned int max,
char **result, unsigned int *result_len)
{
int rc;
char *compressed = NULL;
char *uncompressed = strdup(data);
#ifdef CLOCK_MONOTONIC
struct timespec after_t;
struct timespec before_t;
#endif
if (max == 0) {
max = (length * 1.01) + 601; // Size guaranteed to hold result
}
#ifdef CLOCK_MONOTONIC
clock_gettime(CLOCK_MONOTONIC, &before_t);
#endif
compressed = calloc((size_t) max, sizeof(char));
CRM_ASSERT(compressed);
*result_len = max;
rc = BZ2_bzBuffToBuffCompress(compressed, result_len, uncompressed, length,
CRM_BZ2_BLOCKS, 0, CRM_BZ2_WORK);
free(uncompressed);
if (rc != BZ_OK) {
crm_err("Compression of %d bytes failed: %s " CRM_XS " bzerror=%d",
length, bz2_strerror(rc), rc);
free(compressed);
return pcmk_rc_error;
}
#ifdef CLOCK_MONOTONIC
clock_gettime(CLOCK_MONOTONIC, &after_t);
crm_trace("Compressed %d bytes into %d (ratio %d:1) in %.0fms",
length, *result_len, length / (*result_len),
(after_t.tv_sec - before_t.tv_sec) * 1000 +
(after_t.tv_nsec - before_t.tv_nsec) / 1e6);
#else
crm_trace("Compressed %d bytes into %d (ratio %d:1)",
length, *result_len, length / (*result_len));
#endif
*result = compressed;
return pcmk_rc_ok;
}
char *
crm_strdup_printf(char const *format, ...)
{
va_list ap;
int len = 0;
char *string = NULL;
va_start(ap, format);
len = vasprintf (&string, format, ap);
CRM_ASSERT(len > 0);
va_end(ap);
return string;
}
int
pcmk__parse_ll_range(const char *srcstring, long long *start, long long *end)
{
char *remainder = NULL;
+ int rc = pcmk_rc_ok;
CRM_ASSERT(start != NULL && end != NULL);
*start = PCMK__PARSE_INT_DEFAULT;
*end = PCMK__PARSE_INT_DEFAULT;
crm_trace("Attempting to decode: [%s]", srcstring);
- if (pcmk__str_empty(srcstring) || !strcmp(srcstring, "-")) {
- return pcmk_rc_unknown_format;
+ if (pcmk__str_eq(srcstring, "", pcmk__str_null_matches)) {
+ return ENODATA;
+ } else if (pcmk__str_eq(srcstring, "-", pcmk__str_none)) {
+ return pcmk_rc_bad_input;
}
/* String starts with a dash, so this is either a range with
* no beginning or garbage.
* */
if (*srcstring == '-') {
int rc = scan_ll(srcstring+1, end, PCMK__PARSE_INT_DEFAULT, &remainder);
if (rc != pcmk_rc_ok || *remainder != '\0') {
- return pcmk_rc_unknown_format;
+ return pcmk_rc_bad_input;
} else {
return pcmk_rc_ok;
}
}
- if (scan_ll(srcstring, start, PCMK__PARSE_INT_DEFAULT,
- &remainder) != pcmk_rc_ok) {
- return pcmk_rc_unknown_format;
+ rc = scan_ll(srcstring, start, PCMK__PARSE_INT_DEFAULT, &remainder);
+ if (rc != pcmk_rc_ok) {
+ return rc;
}
if (*remainder && *remainder == '-') {
if (*(remainder+1)) {
char *more_remainder = NULL;
int rc = scan_ll(remainder+1, end, PCMK__PARSE_INT_DEFAULT,
&more_remainder);
- if (rc != pcmk_rc_ok || *more_remainder != '\0') {
- return pcmk_rc_unknown_format;
+ if (rc != pcmk_rc_ok) {
+ return rc;
+ } else if (*more_remainder != '\0') {
+ return pcmk_rc_bad_input;
}
}
} else if (*remainder && *remainder != '-') {
*start = PCMK__PARSE_INT_DEFAULT;
- return pcmk_rc_unknown_format;
+ return pcmk_rc_bad_input;
} else {
/* The input string contained only one number. Set start and end
* to the same value and return pcmk_rc_ok. This gives the caller
* a way to tell this condition apart from a range with no end.
*/
*end = *start;
}
return pcmk_rc_ok;
}
/*!
* \internal
* \brief Find a string in a list of strings
*
* \note This function takes the same flags and has the same behavior as
* pcmk__str_eq().
*
* \note No matter what input string or flags are provided, an empty
* list will always return FALSE.
*
* \param[in] s String to search for
* \param[in] lst List to search
* \param[in] flags A bitfield of pcmk__str_flags to modify operation
*
* \return \c TRUE if \p s is in \p lst, or \c FALSE otherwise
*/
gboolean
pcmk__str_in_list(const gchar *s, const GList *lst, uint32_t flags)
{
for (const GList *ele = lst; ele != NULL; ele = ele->next) {
if (pcmk__str_eq(s, ele->data, flags)) {
return TRUE;
}
}
return FALSE;
}
static bool
str_any_of(const char *s, va_list args, uint32_t flags)
{
if (s == NULL) {
return pcmk_is_set(flags, pcmk__str_null_matches);
}
while (1) {
const char *ele = va_arg(args, const char *);
if (ele == NULL) {
break;
} else if (pcmk__str_eq(s, ele, flags)) {
return true;
}
}
return false;
}
/*!
* \internal
* \brief Is a string a member of a list of strings?
*
* \param[in] s String to search for in \p ...
* \param[in] ... Strings to compare \p s against. The final string
* must be NULL.
*
* \note The comparison is done case-insensitively. The function name is
* meant to be reminiscent of strcasecmp.
*
* \return \c true if \p s is in \p ..., or \c false otherwise
*/
bool
pcmk__strcase_any_of(const char *s, ...)
{
va_list ap;
bool rc;
va_start(ap, s);
rc = str_any_of(s, ap, pcmk__str_casei);
va_end(ap);
return rc;
}
/*!
* \internal
* \brief Is a string a member of a list of strings?
*
* \param[in] s String to search for in \p ...
* \param[in] ... Strings to compare \p s against. The final string
* must be NULL.
*
* \note The comparison is done taking case into account.
*
* \return \c true if \p s is in \p ..., or \c false otherwise
*/
bool
pcmk__str_any_of(const char *s, ...)
{
va_list ap;
bool rc;
va_start(ap, s);
rc = str_any_of(s, ap, pcmk__str_none);
va_end(ap);
return rc;
}
/*!
* \internal
* \brief Check whether a character is in any of a list of strings
*
* \param[in] ch Character (ASCII) to search for
* \param[in] ... Strings to search. Final argument must be
* \c NULL.
*
* \return \c true if any of \p ... contain \p ch, \c false otherwise
* \note \p ... must contain at least one argument (\c NULL).
*/
bool
pcmk__char_in_any_str(int ch, ...)
{
bool rc = false;
va_list ap;
/*
* Passing a char to va_start() can generate compiler warnings,
* so ch is declared as an int.
*/
va_start(ap, ch);
while (1) {
const char *ele = va_arg(ap, const char *);
if (ele == NULL) {
break;
} else if (strchr(ele, ch) != NULL) {
rc = true;
break;
}
}
va_end(ap);
return rc;
}
/*!
* \internal
* \brief Sort strings, with numeric portions sorted numerically
*
* Sort two strings case-insensitively like strcasecmp(), but with any numeric
* portions of the string sorted numerically. This is particularly useful for
* node names (for example, "node10" will sort higher than "node9" but lower
* than "remotenode9").
*
* \param[in] s1 First string to compare (must not be NULL)
* \param[in] s2 Second string to compare (must not be NULL)
*
* \retval -1 \p s1 comes before \p s2
* \retval 0 \p s1 and \p s2 are equal
* \retval 1 \p s1 comes after \p s2
*/
int
pcmk__numeric_strcasecmp(const char *s1, const char *s2)
{
CRM_ASSERT((s1 != NULL) && (s2 != NULL));
while (*s1 && *s2) {
if (isdigit(*s1) && isdigit(*s2)) {
// If node names contain a number, sort numerically
char *end1 = NULL;
char *end2 = NULL;
long num1 = strtol(s1, &end1, 10);
long num2 = strtol(s2, &end2, 10);
// allow ordering e.g. 007 > 7
size_t len1 = end1 - s1;
size_t len2 = end2 - s2;
if (num1 < num2) {
return -1;
} else if (num1 > num2) {
return 1;
} else if (len1 < len2) {
return -1;
} else if (len1 > len2) {
return 1;
}
s1 = end1;
s2 = end2;
} else {
// Compare non-digits case-insensitively
int lower1 = tolower(*s1);
int lower2 = tolower(*s2);
if (lower1 < lower2) {
return -1;
} else if (lower1 > lower2) {
return 1;
}
++s1;
++s2;
}
}
if (!*s1 && *s2) {
return -1;
} else if (*s1 && !*s2) {
return 1;
}
return 0;
}
/*!
* \internal
* \brief Sort strings.
*
* This is your one-stop function for string comparison. By default, this
* function works like \p g_strcmp0. That is, like \p strcmp but a \p NULL
* string sorts before a non-NULL string.
*
* The \p pcmk__str_none flag produces the default behavior. Behavior can be
* changed with various flags:
*
* - \p pcmk__str_regex - The second string is a regular expression that the
* first string will be matched against.
* - \p pcmk__str_casei - By default, comparisons are done taking case into
* account. This flag makes comparisons case-
* insensitive. This can be combined with
* \p pcmk__str_regex.
* - \p pcmk__str_null_matches - If one string is \p NULL and the other is not,
* still return \p 0.
* - \p pcmk__str_star_matches - If one string is \p "*" and the other is not,
* still return \p 0.
*
* \param[in] s1 First string to compare
* \param[in] s2 Second string to compare, or a regular expression to
* match if \p pcmk__str_regex is set
* \param[in] flags A bitfield of \p pcmk__str_flags to modify operation
*
* \retval negative \p s1 is \p NULL or comes before \p s2
* \retval 0 \p s1 and \p s2 are equal, or \p s1 is found in \p s2 if
* \c pcmk__str_regex is set
* \retval positive \p s2 is \p NULL or \p s1 comes after \p s2, or \p s2
* is an invalid regular expression, or \p s1 was not found
* in \p s2 if \p pcmk__str_regex is set.
*/
int
pcmk__strcmp(const char *s1, const char *s2, uint32_t flags)
{
/* If this flag is set, the second string is a regex. */
if (pcmk_is_set(flags, pcmk__str_regex)) {
regex_t r_patt;
int reg_flags = REG_EXTENDED | REG_NOSUB;
int regcomp_rc = 0;
int rc = 0;
if (s1 == NULL || s2 == NULL) {
return 1;
}
if (pcmk_is_set(flags, pcmk__str_casei)) {
reg_flags |= REG_ICASE;
}
regcomp_rc = regcomp(&r_patt, s2, reg_flags);
if (regcomp_rc != 0) {
rc = 1;
crm_err("Bad regex '%s' for update: %s", s2, strerror(regcomp_rc));
} else {
rc = regexec(&r_patt, s1, 0, NULL, 0);
regfree(&r_patt);
if (rc != 0) {
rc = 1;
}
}
return rc;
}
/* If the strings are the same pointer, return 0 immediately. */
if (s1 == s2) {
return 0;
}
/* If this flag is set, return 0 if either (or both) of the input strings
* are NULL. If neither one is NULL, we need to continue and compare
* them normally.
*/
if (pcmk_is_set(flags, pcmk__str_null_matches)) {
if (s1 == NULL || s2 == NULL) {
return 0;
}
}
/* Handle the cases where one is NULL and the str_null_matches flag is not set.
* A NULL string always sorts to the beginning.
*/
if (s1 == NULL) {
return -1;
} else if (s2 == NULL) {
return 1;
}
/* If this flag is set, return 0 if either (or both) of the input strings
* are "*". If neither one is, we need to continue and compare them
* normally.
*/
if (pcmk_is_set(flags, pcmk__str_star_matches)) {
if (strcmp(s1, "*") == 0 || strcmp(s2, "*") == 0) {
return 0;
}
}
if (pcmk_is_set(flags, pcmk__str_casei)) {
return strcasecmp(s1, s2);
} else {
return strcmp(s1, s2);
}
}
/*!
* \internal
* \brief Update a dynamically allocated string with a new value
*
* Given a dynamically allocated string and a new value for it, if the string
* is different from the new value, free the string and replace it with either a
* newly allocated duplicate of the value or NULL as appropriate.
*
* \param[in,out] str Pointer to dynamically allocated string
* \param[in] value New value to duplicate (or NULL)
*
* \note The caller remains responsibile for freeing \p *str.
*/
void
pcmk__str_update(char **str, const char *value)
{
if ((str != NULL) && !pcmk__str_eq(*str, value, pcmk__str_none)) {
free(*str);
if (value == NULL) {
*str = NULL;
} else {
*str = strdup(value);
CRM_ASSERT(*str != NULL);
}
}
}
/*!
* \internal
* \brief Append a list of strings to a destination \p GString
*
* \param[in,out] buffer Where to append the strings (must not be \p NULL)
* \param[in] ... A NULL-terminated list of strings
*
* \note This tends to be more efficient than a single call to
* \p g_string_append_printf().
*/
void
pcmk__g_strcat(GString *buffer, ...)
{
va_list ap;
CRM_ASSERT(buffer != NULL);
va_start(ap, buffer);
while (true) {
const char *ele = va_arg(ap, const char *);
if (ele == NULL) {
break;
}
g_string_append(buffer, ele);
}
va_end(ap);
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include
gboolean
safe_str_neq(const char *a, const char *b)
{
if (a == b) {
return FALSE;
} else if (a == NULL || b == NULL) {
return TRUE;
} else if (strcasecmp(a, b) == 0) {
return FALSE;
}
return TRUE;
}
gboolean
crm_str_eq(const char *a, const char *b, gboolean use_case)
{
if (use_case) {
return g_strcmp0(a, b) == 0;
/* TODO - Figure out which calls, if any, really need to be case independent */
} else if (a == b) {
return TRUE;
} else if (a == NULL || b == NULL) {
/* shouldn't be comparing NULLs */
return FALSE;
} else if (strcasecmp(a, b) == 0) {
return TRUE;
}
return FALSE;
}
char *
crm_itoa_stack(int an_int, char *buffer, size_t len)
{
if (buffer != NULL) {
snprintf(buffer, len, "%d", an_int);
}
return buffer;
}
guint
g_str_hash_traditional(gconstpointer v)
{
return pcmk__str_hash(v);
}
gboolean
crm_strcase_equal(gconstpointer a, gconstpointer b)
{
return pcmk__strcase_equal(a, b);
}
guint
crm_strcase_hash(gconstpointer v)
{
return pcmk__strcase_hash(v);
}
GHashTable *
crm_str_table_dup(GHashTable *old_table)
{
return pcmk__str_table_dup(old_table);
}
long long
crm_parse_ll(const char *text, const char *default_text)
{
long long result;
if (text == NULL) {
text = default_text;
if (text == NULL) {
crm_err("No default conversion value supplied");
errno = EINVAL;
return PCMK__PARSE_INT_DEFAULT;
}
}
scan_ll(text, &result, PCMK__PARSE_INT_DEFAULT, NULL);
return result;
}
int
crm_parse_int(const char *text, const char *default_text)
{
long long result = crm_parse_ll(text, default_text);
if (result < INT_MIN) {
// If errno is ERANGE, crm_parse_ll() has already logged a message
if (errno != ERANGE) {
crm_err("Conversion of %s was clipped: %lld", text, result);
errno = ERANGE;
}
return INT_MIN;
} else if (result > INT_MAX) {
// If errno is ERANGE, crm_parse_ll() has already logged a message
if (errno != ERANGE) {
crm_err("Conversion of %s was clipped: %lld", text, result);
errno = ERANGE;
}
return INT_MAX;
}
return (int) result;
}
char *
crm_strip_trailing_newline(char *str)
{
return pcmk__trim(str);
}
int
pcmk_numeric_strcasecmp(const char *s1, const char *s2)
{
return pcmk__numeric_strcasecmp(s1, s2);
}
// LCOV_EXCL_STOP
// End deprecated API
diff --git a/lib/common/tests/nvpair/pcmk__xe_get_bool_attr_test.c b/lib/common/tests/nvpair/pcmk__xe_get_bool_attr_test.c
index 318d90e7ab..500d8a6675 100644
--- a/lib/common/tests/nvpair/pcmk__xe_get_bool_attr_test.c
+++ b/lib/common/tests/nvpair/pcmk__xe_get_bool_attr_test.c
@@ -1,59 +1,59 @@
/*
- * Copyright 2021 the Pacemaker project contributors
+ * Copyright 2021-2023 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
#include
static void
empty_input(void **state)
{
xmlNode *node = string2xml("");
bool value;
assert_int_equal(pcmk__xe_get_bool_attr(NULL, NULL, &value), ENODATA);
assert_int_equal(pcmk__xe_get_bool_attr(NULL, "whatever", &value), ENODATA);
assert_int_equal(pcmk__xe_get_bool_attr(node, NULL, &value), EINVAL);
assert_int_equal(pcmk__xe_get_bool_attr(node, "whatever", NULL), EINVAL);
free_xml(node);
}
static void
attr_missing(void **state)
{
xmlNode *node = string2xml("");
bool value;
assert_int_equal(pcmk__xe_get_bool_attr(node, "c", &value), ENODATA);
free_xml(node);
}
static void
attr_present(void **state)
{
xmlNode *node = string2xml("");
bool value;
value = false;
assert_int_equal(pcmk__xe_get_bool_attr(node, "a", &value), pcmk_rc_ok);
assert_true(value);
value = true;
assert_int_equal(pcmk__xe_get_bool_attr(node, "b", &value), pcmk_rc_ok);
assert_false(value);
- assert_int_equal(pcmk__xe_get_bool_attr(node, "c", &value), pcmk_rc_unknown_format);
+ assert_int_equal(pcmk__xe_get_bool_attr(node, "c", &value), pcmk_rc_bad_input);
free_xml(node);
}
PCMK__UNIT_TEST(NULL, NULL,
cmocka_unit_test(empty_input),
cmocka_unit_test(attr_missing),
cmocka_unit_test(attr_present))
diff --git a/lib/common/tests/strings/pcmk__parse_ll_range_test.c b/lib/common/tests/strings/pcmk__parse_ll_range_test.c
index dca1fc1b5e..7656ad7e3b 100644
--- a/lib/common/tests/strings/pcmk__parse_ll_range_test.c
+++ b/lib/common/tests/strings/pcmk__parse_ll_range_test.c
@@ -1,116 +1,117 @@
/*
- * Copyright 2020-2022 the Pacemaker project contributors
+ * Copyright 2020-2023 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 "crm/common/results.h"
#include
#include
static void
empty_input_string(void **state)
{
long long start, end;
- assert_int_equal(pcmk__parse_ll_range(NULL, &start, &end), pcmk_rc_unknown_format);
- assert_int_equal(pcmk__parse_ll_range("", &start, &end), pcmk_rc_unknown_format);
+ assert_int_equal(pcmk__parse_ll_range(NULL, &start, &end), ENODATA);
+ assert_int_equal(pcmk__parse_ll_range("", &start, &end), ENODATA);
}
static void
null_input_variables(void **state)
{
long long start, end;
pcmk__assert_asserts(pcmk__parse_ll_range("1234", NULL, &end));
pcmk__assert_asserts(pcmk__parse_ll_range("1234", &start, NULL));
}
static void
missing_separator(void **state)
{
long long start, end;
assert_int_equal(pcmk__parse_ll_range("1234", &start, &end), pcmk_rc_ok);
assert_int_equal(start, 1234);
assert_int_equal(end, 1234);
}
static void
only_separator(void **state)
{
long long start, end;
- assert_int_equal(pcmk__parse_ll_range("-", &start, &end), pcmk_rc_unknown_format);
+ assert_int_equal(pcmk__parse_ll_range("-", &start, &end), pcmk_rc_bad_input);
assert_int_equal(start, PCMK__PARSE_INT_DEFAULT);
assert_int_equal(end, PCMK__PARSE_INT_DEFAULT);
}
static void
no_range_end(void **state)
{
long long start, end;
assert_int_equal(pcmk__parse_ll_range("2000-", &start, &end), pcmk_rc_ok);
assert_int_equal(start, 2000);
assert_int_equal(end, PCMK__PARSE_INT_DEFAULT);
}
static void
no_range_start(void **state)
{
long long start, end;
assert_int_equal(pcmk__parse_ll_range("-2020", &start, &end), pcmk_rc_ok);
assert_int_equal(start, PCMK__PARSE_INT_DEFAULT);
assert_int_equal(end, 2020);
}
static void
range_start_and_end(void **state)
{
long long start, end;
assert_int_equal(pcmk__parse_ll_range("2000-2020", &start, &end), pcmk_rc_ok);
assert_int_equal(start, 2000);
assert_int_equal(end, 2020);
- assert_int_equal(pcmk__parse_ll_range("2000-2020-2030", &start, &end), pcmk_rc_unknown_format);
+ assert_int_equal(pcmk__parse_ll_range("2000-2020-2030", &start, &end), pcmk_rc_bad_input);
}
static void
garbage(void **state)
{
long long start, end;
- assert_int_equal(pcmk__parse_ll_range("2000x-", &start, &end), pcmk_rc_unknown_format);
+ assert_int_equal(pcmk__parse_ll_range("2000x-", &start, &end), pcmk_rc_bad_input);
assert_int_equal(start, PCMK__PARSE_INT_DEFAULT);
assert_int_equal(end, PCMK__PARSE_INT_DEFAULT);
- assert_int_equal(pcmk__parse_ll_range("-x2000", &start, &end), pcmk_rc_unknown_format);
+ assert_int_equal(pcmk__parse_ll_range("-x2000", &start, &end), pcmk_rc_bad_input);
assert_int_equal(start, PCMK__PARSE_INT_DEFAULT);
assert_int_equal(end, PCMK__PARSE_INT_DEFAULT);
}
static void
strtoll_errors(void **state)
{
long long start, end;
- assert_int_equal(pcmk__parse_ll_range("20000000000000000000-", &start, &end), pcmk_rc_unknown_format);
- assert_int_equal(pcmk__parse_ll_range("100-20000000000000000000", &start, &end), pcmk_rc_unknown_format);
+ assert_int_equal(pcmk__parse_ll_range("20000000000000000000-", &start, &end), EOVERFLOW);
+ assert_int_equal(pcmk__parse_ll_range("100-20000000000000000000", &start, &end), EOVERFLOW);
}
PCMK__UNIT_TEST(NULL, NULL,
cmocka_unit_test(empty_input_string),
cmocka_unit_test(null_input_variables),
cmocka_unit_test(missing_separator),
cmocka_unit_test(only_separator),
cmocka_unit_test(no_range_end),
cmocka_unit_test(no_range_start),
cmocka_unit_test(range_start_and_end),
cmocka_unit_test(strtoll_errors),
cmocka_unit_test(garbage))
diff --git a/lib/pengine/rules.c b/lib/pengine/rules.c
index f5ca8da410..4153ce2b70 100644
--- a/lib/pengine/rules.c
+++ b/lib/pengine/rules.c
@@ -1,1311 +1,1311 @@
/*
- * Copyright 2004-2022 the Pacemaker project contributors
+ * Copyright 2004-2023 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
#include
CRM_TRACE_INIT_DATA(pe_rules);
/*!
* \brief Evaluate any rules contained by given XML element
*
* \param[in,out] xml XML element to check for rules
* \param[in] node_hash Node attributes to use to evaluate expressions
* \param[in] now Time to use when evaluating expressions
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return TRUE if no rules, or any of rules present is in effect, else FALSE
*/
gboolean
pe_evaluate_rules(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now,
crm_time_t *next_change)
{
pe_rule_eval_data_t rule_data = {
.node_hash = node_hash,
.role = RSC_ROLE_UNKNOWN,
.now = now,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
return pe_eval_rules(ruleset, &rule_data, next_change);
}
gboolean
pe_test_rule(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role,
crm_time_t *now, crm_time_t *next_change,
pe_match_data_t *match_data)
{
pe_rule_eval_data_t rule_data = {
.node_hash = node_hash,
.role = role,
.now = now,
.match_data = match_data,
.rsc_data = NULL,
.op_data = NULL
};
return pe_eval_expr(rule, &rule_data, next_change);
}
/*!
* \brief Evaluate one rule subelement (pass/fail)
*
* A rule element may contain another rule, a node attribute expression, or a
* date expression. Given any one of those, evaluate it and return whether it
* passed.
*
* \param[in,out] expr Rule subelement XML
* \param[in] node_hash Node attributes to use when evaluating expression
* \param[in] role Resource role to use when evaluating expression
* \param[in] now Time to use when evaluating expression
* \param[out] next_change If not NULL, set to when evaluation will change
* \param[in] match_data If not NULL, resource back-references and params
*
* \return TRUE if expression is in effect under given conditions, else FALSE
*/
gboolean
pe_test_expression(xmlNode *expr, GHashTable *node_hash, enum rsc_role_e role,
crm_time_t *now, crm_time_t *next_change,
pe_match_data_t *match_data)
{
pe_rule_eval_data_t rule_data = {
.node_hash = node_hash,
.role = role,
.now = now,
.match_data = match_data,
.rsc_data = NULL,
.op_data = NULL
};
return pe_eval_subexpr(expr, &rule_data, next_change);
}
enum expression_type
find_expression_type(xmlNode * expr)
{
const char *tag = NULL;
const char *attr = NULL;
attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE);
tag = crm_element_name(expr);
if (pcmk__str_eq(tag, PCMK_XE_DATE_EXPRESSION, pcmk__str_none)) {
return time_expr;
} else if (pcmk__str_eq(tag, PCMK_XE_RSC_EXPRESSION, pcmk__str_none)) {
return rsc_expr;
} else if (pcmk__str_eq(tag, PCMK_XE_OP_EXPRESSION, pcmk__str_none)) {
return op_expr;
} else if (pcmk__str_eq(tag, XML_TAG_RULE, pcmk__str_none)) {
return nested_rule;
} else if (!pcmk__str_eq(tag, XML_TAG_EXPRESSION, pcmk__str_none)) {
return not_expr;
} else if (pcmk__str_any_of(attr, CRM_ATTR_UNAME, CRM_ATTR_KIND, CRM_ATTR_ID, NULL)) {
return loc_expr;
} else if (pcmk__str_eq(attr, CRM_ATTR_ROLE, pcmk__str_none)) {
return role_expr;
}
return attr_expr;
}
/* As per the nethack rules:
*
* moon period = 29.53058 days ~= 30, year = 365.2422 days
* days moon phase advances on first day of year compared to preceding year
* = 365.2422 - 12*29.53058 ~= 11
* years in Metonic cycle (time until same phases fall on the same days of
* the month) = 18.6 ~= 19
* moon phase on first day of year (epact) ~= (11*(year%19) + 29) % 30
* (29 as initial condition)
* current phase in days = first day phase + days elapsed in year
* 6 moons ~= 177 days
* 177 ~= 8 reported phases * 22
* + 11/22 for rounding
*
* 0-7, with 0: new, 4: full
*/
static int
phase_of_the_moon(const crm_time_t *now)
{
uint32_t epact, diy, goldn;
uint32_t y;
crm_time_get_ordinal(now, &y, &diy);
goldn = (y % 19) + 1;
epact = (11 * goldn + 18) % 30;
if ((epact == 25 && goldn > 11) || epact == 24)
epact++;
return ((((((diy + epact) * 6) + 11) % 177) / 22) & 7);
}
static int
check_one(const xmlNode *cron_spec, const char *xml_field, uint32_t time_field)
{
int rc = pcmk_rc_undetermined;
const char *value = crm_element_value(cron_spec, xml_field);
long long low, high;
if (value == NULL) {
/* Return pe_date_result_undetermined if the field is missing. */
goto bail;
}
- if (pcmk__parse_ll_range(value, &low, &high) == pcmk_rc_unknown_format) {
+ if (pcmk__parse_ll_range(value, &low, &high) != pcmk_rc_ok) {
goto bail;
} else if (low == high) {
/* A single number was given, not a range. */
if (time_field < low) {
rc = pcmk_rc_before_range;
} else if (time_field > high) {
rc = pcmk_rc_after_range;
} else {
rc = pcmk_rc_within_range;
}
} else if (low != -1 && high != -1) {
/* This is a range with both bounds. */
if (time_field < low) {
rc = pcmk_rc_before_range;
} else if (time_field > high) {
rc = pcmk_rc_after_range;
} else {
rc = pcmk_rc_within_range;
}
} else if (low == -1) {
/* This is a range with no starting value. */
rc = time_field <= high ? pcmk_rc_within_range : pcmk_rc_after_range;
} else if (high == -1) {
/* This is a range with no ending value. */
rc = time_field >= low ? pcmk_rc_within_range : pcmk_rc_before_range;
}
bail:
if (rc == pcmk_rc_within_range) {
crm_debug("Condition '%s' in %s: passed", value, xml_field);
} else {
crm_debug("Condition '%s' in %s: failed", value, xml_field);
}
return rc;
}
static gboolean
check_passes(int rc) {
/* _within_range is obvious. _undetermined is a pass because
* this is the return value if a field is not given. In this
* case, we just want to ignore it and check other fields to
* see if they place some restriction on what can pass.
*/
return rc == pcmk_rc_within_range || rc == pcmk_rc_undetermined;
}
#define CHECK_ONE(spec, name, var) do { \
int subpart_rc = check_one(spec, name, var); \
if (check_passes(subpart_rc) == FALSE) { \
return subpart_rc; \
} \
} while (0)
int
pe_cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec)
{
uint32_t h, m, s, y, d, w;
CRM_CHECK(now != NULL, return pcmk_rc_op_unsatisfied);
crm_time_get_gregorian(now, &y, &m, &d);
CHECK_ONE(cron_spec, "years", y);
CHECK_ONE(cron_spec, "months", m);
CHECK_ONE(cron_spec, "monthdays", d);
crm_time_get_timeofday(now, &h, &m, &s);
CHECK_ONE(cron_spec, "hours", h);
CHECK_ONE(cron_spec, "minutes", m);
CHECK_ONE(cron_spec, "seconds", s);
crm_time_get_ordinal(now, &y, &d);
CHECK_ONE(cron_spec, "yeardays", d);
crm_time_get_isoweek(now, &y, &w, &d);
CHECK_ONE(cron_spec, "weekyears", y);
CHECK_ONE(cron_spec, "weeks", w);
CHECK_ONE(cron_spec, "weekdays", d);
CHECK_ONE(cron_spec, "moon", phase_of_the_moon(now));
/* If we get here, either no fields were specified (which is success), or all
* the fields that were specified had their conditions met (which is also a
* success). Thus, the result is success.
*/
return pcmk_rc_ok;
}
static void
update_field(crm_time_t *t, const xmlNode *xml, const char *attr,
void (*time_fn)(crm_time_t *, int))
{
long long value;
if ((pcmk__scan_ll(crm_element_value(xml, attr), &value, 0LL) == pcmk_rc_ok)
&& (value != 0LL) && (value >= INT_MIN) && (value <= INT_MAX)) {
time_fn(t, (int) value);
}
}
static crm_time_t *
parse_xml_duration(const crm_time_t *start, const xmlNode *duration_spec)
{
crm_time_t *end = pcmk_copy_time(start);
update_field(end, duration_spec, "years", crm_time_add_years);
update_field(end, duration_spec, "months", crm_time_add_months);
update_field(end, duration_spec, "weeks", crm_time_add_weeks);
update_field(end, duration_spec, "days", crm_time_add_days);
update_field(end, duration_spec, "hours", crm_time_add_hours);
update_field(end, duration_spec, "minutes", crm_time_add_minutes);
update_field(end, duration_spec, "seconds", crm_time_add_seconds);
return end;
}
// Set next_change to t if t is earlier
static void
crm_time_set_if_earlier(crm_time_t *next_change, crm_time_t *t)
{
if ((next_change != NULL) && (t != NULL)) {
if (!crm_time_is_defined(next_change)
|| (crm_time_compare(t, next_change) < 0)) {
crm_time_set(next_change, t);
}
}
}
// Information about a block of nvpair elements
typedef struct sorted_set_s {
int score; // This block's score for sorting
const char *name; // This block's ID
const char *special_name; // ID that should sort first
xmlNode *attr_set; // This block
} sorted_set_t;
static gint
sort_pairs(gconstpointer a, gconstpointer b)
{
const sorted_set_t *pair_a = a;
const sorted_set_t *pair_b = b;
if (a == NULL && b == NULL) {
return 0;
} else if (a == NULL) {
return 1;
} else if (b == NULL) {
return -1;
}
if (pcmk__str_eq(pair_a->name, pair_a->special_name, pcmk__str_casei)) {
return -1;
} else if (pcmk__str_eq(pair_b->name, pair_a->special_name, pcmk__str_casei)) {
return 1;
}
if (pair_a->score < pair_b->score) {
return 1;
} else if (pair_a->score > pair_b->score) {
return -1;
}
return 0;
}
static void
populate_hash(xmlNode * nvpair_list, GHashTable * hash, gboolean overwrite, xmlNode * top)
{
const char *name = NULL;
const char *value = NULL;
const char *old_value = NULL;
xmlNode *list = nvpair_list;
xmlNode *an_attr = NULL;
name = crm_element_name(list->children);
if (pcmk__str_eq(XML_TAG_ATTRS, name, pcmk__str_casei)) {
list = list->children;
}
for (an_attr = pcmk__xe_first_child(list); an_attr != NULL;
an_attr = pcmk__xe_next(an_attr)) {
if (pcmk__str_eq((const char *)an_attr->name, XML_CIB_TAG_NVPAIR, pcmk__str_none)) {
xmlNode *ref_nvpair = expand_idref(an_attr, top);
name = crm_element_value(an_attr, XML_NVPAIR_ATTR_NAME);
if (name == NULL) {
name = crm_element_value(ref_nvpair, XML_NVPAIR_ATTR_NAME);
}
value = crm_element_value(an_attr, XML_NVPAIR_ATTR_VALUE);
if (value == NULL) {
value = crm_element_value(ref_nvpair, XML_NVPAIR_ATTR_VALUE);
}
if (name == NULL || value == NULL) {
continue;
}
old_value = g_hash_table_lookup(hash, name);
if (pcmk__str_eq(value, "#default", pcmk__str_casei)) {
if (old_value) {
crm_trace("Letting %s default (removing explicit value \"%s\")",
name, value);
g_hash_table_remove(hash, name);
}
continue;
} else if (old_value == NULL) {
crm_trace("Setting %s=\"%s\"", name, value);
g_hash_table_insert(hash, strdup(name), strdup(value));
} else if (overwrite) {
crm_trace("Setting %s=\"%s\" (overwriting old value \"%s\")",
name, value, old_value);
g_hash_table_replace(hash, strdup(name), strdup(value));
}
}
}
}
typedef struct unpack_data_s {
gboolean overwrite;
void *hash;
crm_time_t *next_change;
const pe_rule_eval_data_t *rule_data;
xmlNode *top;
} unpack_data_t;
static void
unpack_attr_set(gpointer data, gpointer user_data)
{
sorted_set_t *pair = data;
unpack_data_t *unpack_data = user_data;
if (!pe_eval_rules(pair->attr_set, unpack_data->rule_data,
unpack_data->next_change)) {
return;
}
crm_trace("Adding attributes from %s (score %d) %s overwrite",
pair->name, pair->score,
(unpack_data->overwrite? "with" : "without"));
populate_hash(pair->attr_set, unpack_data->hash, unpack_data->overwrite, unpack_data->top);
}
/*!
* \internal
* \brief Create a sorted list of nvpair blocks
*
* \param[in,out] top XML document root (used to expand id-ref's)
* \param[in] xml_obj XML element containing blocks of nvpair elements
* \param[in] set_name If not NULL, only get blocks of this element
* \param[in] always_first If not NULL, sort block with this ID as first
*
* \return List of sorted_set_t entries for nvpair blocks
*/
static GList *
make_pairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name,
const char *always_first)
{
GList *unsorted = NULL;
if (xml_obj == NULL) {
return NULL;
}
for (xmlNode *attr_set = pcmk__xe_first_child(xml_obj); attr_set != NULL;
attr_set = pcmk__xe_next(attr_set)) {
if (pcmk__str_eq(set_name, (const char *) attr_set->name,
pcmk__str_null_matches)) {
const char *score = NULL;
sorted_set_t *pair = NULL;
xmlNode *expanded_attr_set = expand_idref(attr_set, top);
if (expanded_attr_set == NULL) {
// Schema (if not "none") prevents this
continue;
}
pair = calloc(1, sizeof(sorted_set_t));
pair->name = ID(expanded_attr_set);
pair->special_name = always_first;
pair->attr_set = expanded_attr_set;
score = crm_element_value(expanded_attr_set, XML_RULE_ATTR_SCORE);
pair->score = char2score(score);
unsorted = g_list_prepend(unsorted, pair);
}
}
return g_list_sort(unsorted, sort_pairs);
}
/*!
* \brief Extract nvpair blocks contained by an XML element into a hash table
*
* \param[in,out] top XML document root (used to expand id-ref's)
* \param[in] xml_obj XML element containing blocks of nvpair elements
* \param[in] set_name If not NULL, only use blocks of this element
* \param[in] rule_data Matching parameters to use when unpacking
* \param[out] hash Where to store extracted name/value pairs
* \param[in] always_first If not NULL, process block with this ID first
* \param[in] overwrite Whether to replace existing values with same name
* \param[out] next_change If not NULL, set to when evaluation will change
*/
void
pe_eval_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name,
const pe_rule_eval_data_t *rule_data, GHashTable *hash,
const char *always_first, gboolean overwrite,
crm_time_t *next_change)
{
GList *pairs = make_pairs(top, xml_obj, set_name, always_first);
if (pairs) {
unpack_data_t data = {
.hash = hash,
.overwrite = overwrite,
.next_change = next_change,
.top = top,
.rule_data = rule_data
};
g_list_foreach(pairs, unpack_attr_set, &data);
g_list_free_full(pairs, free);
}
}
/*!
* \brief Extract nvpair blocks contained by an XML element into a hash table
*
* \param[in,out] top XML document root (used to expand id-ref's)
* \param[in] xml_obj XML element containing blocks of nvpair elements
* \param[in] set_name Element name to identify nvpair blocks
* \param[in] node_hash Node attributes to use when evaluating rules
* \param[out] hash Where to store extracted name/value pairs
* \param[in] always_first If not NULL, process block with this ID first
* \param[in] overwrite Whether to replace existing values with same name
* \param[in] now Time to use when evaluating rules
* \param[out] next_change If not NULL, set to when evaluation will change
*/
void
pe_unpack_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name,
GHashTable *node_hash, GHashTable *hash,
const char *always_first, gboolean overwrite,
crm_time_t *now, crm_time_t *next_change)
{
pe_rule_eval_data_t rule_data = {
.node_hash = node_hash,
.role = RSC_ROLE_UNKNOWN,
.now = now,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
pe_eval_nvpairs(top, xml_obj, set_name, &rule_data, hash,
always_first, overwrite, next_change);
}
/*!
* \brief Expand any regular expression submatches (%0-%9) in a string
*
* \param[in] string String possibly containing submatch variables
* \param[in] match_data If not NULL, regular expression matches
*
* \return Newly allocated string identical to \p string with submatches
* expanded, or NULL if there were no matches
*/
char *
pe_expand_re_matches(const char *string, const pe_re_match_data_t *match_data)
{
size_t len = 0;
int i;
const char *p, *last_match_index;
char *p_dst, *result = NULL;
if (pcmk__str_empty(string) || !match_data) {
return NULL;
}
p = last_match_index = string;
while (*p) {
if (*p == '%' && *(p + 1) && isdigit(*(p + 1))) {
i = *(p + 1) - '0';
if (match_data->nregs >= i && match_data->pmatch[i].rm_so != -1 &&
match_data->pmatch[i].rm_eo > match_data->pmatch[i].rm_so) {
len += p - last_match_index + (match_data->pmatch[i].rm_eo - match_data->pmatch[i].rm_so);
last_match_index = p + 2;
}
p++;
}
p++;
}
len += p - last_match_index + 1;
/* FIXME: Excessive? */
if (len - 1 <= 0) {
return NULL;
}
p_dst = result = calloc(1, len);
p = string;
while (*p) {
if (*p == '%' && *(p + 1) && isdigit(*(p + 1))) {
i = *(p + 1) - '0';
if (match_data->nregs >= i && match_data->pmatch[i].rm_so != -1 &&
match_data->pmatch[i].rm_eo > match_data->pmatch[i].rm_so) {
/* rm_eo can be equal to rm_so, but then there is nothing to do */
int match_len = match_data->pmatch[i].rm_eo - match_data->pmatch[i].rm_so;
memcpy(p_dst, match_data->string + match_data->pmatch[i].rm_so, match_len);
p_dst += match_len;
}
p++;
} else {
*(p_dst) = *(p);
p_dst++;
}
p++;
}
return result;
}
/*!
* \brief Evaluate rules
*
* \param[in,out] ruleset XML possibly containing rule sub-elements
* \param[in] rule_data
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return TRUE if there are no rules or
*/
gboolean
pe_eval_rules(xmlNode *ruleset, const pe_rule_eval_data_t *rule_data,
crm_time_t *next_change)
{
// If there are no rules, pass by default
gboolean ruleset_default = TRUE;
for (xmlNode *rule = first_named_child(ruleset, XML_TAG_RULE);
rule != NULL; rule = crm_next_same_xml(rule)) {
ruleset_default = FALSE;
if (pe_eval_expr(rule, rule_data, next_change)) {
/* Only the deprecated "lifetime" element of location constraints
* may contain more than one rule at the top level -- the schema
* limits a block of nvpairs to a single top-level rule. So, this
* effectively means that a lifetime is active if any rule it
* contains is active.
*/
return TRUE;
}
}
return ruleset_default;
}
/*!
* \brief Evaluate all of a rule's expressions
*
* \param[in,out] rule XML containing a rule definition or its id-ref
* \param[in] rule_data Matching parameters to check against rule
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return TRUE if \p rule_data passes \p rule, otherwise FALSE
*/
gboolean
pe_eval_expr(xmlNode *rule, const pe_rule_eval_data_t *rule_data,
crm_time_t *next_change)
{
xmlNode *expr = NULL;
gboolean test = TRUE;
gboolean empty = TRUE;
gboolean passed = TRUE;
gboolean do_and = TRUE;
const char *value = NULL;
rule = expand_idref(rule, NULL);
value = crm_element_value(rule, XML_RULE_ATTR_BOOLEAN_OP);
if (pcmk__str_eq(value, "or", pcmk__str_casei)) {
do_and = FALSE;
passed = FALSE;
}
crm_trace("Testing rule %s", ID(rule));
for (expr = pcmk__xe_first_child(rule); expr != NULL;
expr = pcmk__xe_next(expr)) {
test = pe_eval_subexpr(expr, rule_data, next_change);
empty = FALSE;
if (test && do_and == FALSE) {
crm_trace("Expression %s/%s passed", ID(rule), ID(expr));
return TRUE;
} else if (test == FALSE && do_and) {
crm_trace("Expression %s/%s failed", ID(rule), ID(expr));
return FALSE;
}
}
if (empty) {
crm_err("Invalid Rule %s: rules must contain at least one expression", ID(rule));
}
crm_trace("Rule %s %s", ID(rule), passed ? "passed" : "failed");
return passed;
}
/*!
* \brief Evaluate a single rule expression, including any subexpressions
*
* \param[in,out] expr XML containing a rule expression
* \param[in] rule_data Matching parameters to check against expression
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return TRUE if \p rule_data passes \p expr, otherwise FALSE
*/
gboolean
pe_eval_subexpr(xmlNode *expr, const pe_rule_eval_data_t *rule_data,
crm_time_t *next_change)
{
gboolean accept = FALSE;
const char *uname = NULL;
switch (find_expression_type(expr)) {
case nested_rule:
accept = pe_eval_expr(expr, rule_data, next_change);
break;
case attr_expr:
case loc_expr:
/* these expressions can never succeed if there is
* no node to compare with
*/
if (rule_data->node_hash != NULL) {
accept = pe__eval_attr_expr(expr, rule_data);
}
break;
case time_expr:
switch (pe__eval_date_expr(expr, rule_data, next_change)) {
case pcmk_rc_within_range:
case pcmk_rc_ok:
accept = TRUE;
break;
default:
accept = FALSE;
break;
}
break;
case role_expr:
accept = pe__eval_role_expr(expr, rule_data);
break;
case rsc_expr:
accept = pe__eval_rsc_expr(expr, rule_data);
break;
case op_expr:
accept = pe__eval_op_expr(expr, rule_data);
break;
default:
CRM_CHECK(FALSE /* bad type */ , return FALSE);
accept = FALSE;
}
if (rule_data->node_hash) {
uname = g_hash_table_lookup(rule_data->node_hash, CRM_ATTR_UNAME);
}
crm_trace("Expression %s %s on %s",
ID(expr), accept ? "passed" : "failed", uname ? uname : "all nodes");
return accept;
}
/*!
* \internal
* \brief Compare two values in a rule's node attribute expression
*
* \param[in] l_val Value on left-hand side of comparison
* \param[in] r_val Value on right-hand side of comparison
* \param[in] type How to interpret the values (allowed values:
* \c "string", \c "integer", \c "number",
* \c "version", \c NULL)
* \param[in] op Type of comparison
*
* \return -1 if (l_val < r_val),
* 0 if (l_val == r_val),
* 1 if (l_val > r_val)
*/
static int
compare_attr_expr_vals(const char *l_val, const char *r_val, const char *type,
const char *op)
{
int cmp = 0;
if (l_val != NULL && r_val != NULL) {
if (type == NULL) {
if (pcmk__strcase_any_of(op, "lt", "lte", "gt", "gte", NULL)) {
if (pcmk__char_in_any_str('.', l_val, r_val, NULL)) {
type = "number";
} else {
type = "integer";
}
} else {
type = "string";
}
crm_trace("Defaulting to %s based comparison for '%s' op", type, op);
}
if (pcmk__str_eq(type, "string", pcmk__str_casei)) {
cmp = strcasecmp(l_val, r_val);
} else if (pcmk__str_eq(type, "integer", pcmk__str_casei)) {
long long l_val_num;
int rc1 = pcmk__scan_ll(l_val, &l_val_num, 0LL);
long long r_val_num;
int rc2 = pcmk__scan_ll(r_val, &r_val_num, 0LL);
if ((rc1 == pcmk_rc_ok) && (rc2 == pcmk_rc_ok)) {
if (l_val_num < r_val_num) {
cmp = -1;
} else if (l_val_num > r_val_num) {
cmp = 1;
} else {
cmp = 0;
}
} else {
crm_debug("Integer parse error. Comparing %s and %s as strings",
l_val, r_val);
cmp = compare_attr_expr_vals(l_val, r_val, "string", op);
}
} else if (pcmk__str_eq(type, "number", pcmk__str_casei)) {
double l_val_num;
double r_val_num;
int rc1 = pcmk__scan_double(l_val, &l_val_num, NULL, NULL);
int rc2 = pcmk__scan_double(r_val, &r_val_num, NULL, NULL);
if (rc1 == pcmk_rc_ok && rc2 == pcmk_rc_ok) {
if (l_val_num < r_val_num) {
cmp = -1;
} else if (l_val_num > r_val_num) {
cmp = 1;
} else {
cmp = 0;
}
} else {
crm_debug("Floating-point parse error. Comparing %s and %s as "
"strings", l_val, r_val);
cmp = compare_attr_expr_vals(l_val, r_val, "string", op);
}
} else if (pcmk__str_eq(type, "version", pcmk__str_casei)) {
cmp = compare_version(l_val, r_val);
}
} else if (l_val == NULL && r_val == NULL) {
cmp = 0;
} else if (r_val == NULL) {
cmp = 1;
} else { // l_val == NULL && r_val != NULL
cmp = -1;
}
return cmp;
}
/*!
* \internal
* \brief Check whether an attribute expression evaluates to \c true
*
* \param[in] l_val Value on left-hand side of comparison
* \param[in] r_val Value on right-hand side of comparison
* \param[in] type How to interpret the values (allowed values:
* \c "string", \c "integer", \c "number",
* \c "version", \c NULL)
* \param[in] op Type of comparison.
*
* \return \c true if expression evaluates to \c true, \c false
* otherwise
*/
static bool
accept_attr_expr(const char *l_val, const char *r_val, const char *type,
const char *op)
{
int cmp;
if (pcmk__str_eq(op, "defined", pcmk__str_casei)) {
return (l_val != NULL);
} else if (pcmk__str_eq(op, "not_defined", pcmk__str_casei)) {
return (l_val == NULL);
}
cmp = compare_attr_expr_vals(l_val, r_val, type, op);
if (pcmk__str_eq(op, "eq", pcmk__str_casei)) {
return (cmp == 0);
} else if (pcmk__str_eq(op, "ne", pcmk__str_casei)) {
return (cmp != 0);
} else if (l_val == NULL || r_val == NULL) {
// The comparison is meaningless from this point on
return false;
} else if (pcmk__str_eq(op, "lt", pcmk__str_casei)) {
return (cmp < 0);
} else if (pcmk__str_eq(op, "lte", pcmk__str_casei)) {
return (cmp <= 0);
} else if (pcmk__str_eq(op, "gt", pcmk__str_casei)) {
return (cmp > 0);
} else if (pcmk__str_eq(op, "gte", pcmk__str_casei)) {
return (cmp >= 0);
}
return false; // Should never reach this point
}
/*!
* \internal
* \brief Get correct value according to value-source
*
* \param[in] value value given in rule expression
* \param[in] value_source value-source given in rule expressions
* \param[in] match_data If not NULL, resource back-references and params
*/
static const char *
expand_value_source(const char *value, const char *value_source,
const pe_match_data_t *match_data)
{
GHashTable *table = NULL;
if (pcmk__str_empty(value)) {
return NULL; // value_source is irrelevant
} else if (pcmk__str_eq(value_source, "param", pcmk__str_casei)) {
table = match_data->params;
} else if (pcmk__str_eq(value_source, "meta", pcmk__str_casei)) {
table = match_data->meta;
} else { // literal
return value;
}
if (table == NULL) {
return NULL;
}
return (const char *) g_hash_table_lookup(table, value);
}
/*!
* \internal
* \brief Evaluate a node attribute expression based on #uname, #id, #kind,
* or a generic node attribute
*
* \param[in] expr XML of rule expression
* \param[in] rule_data The match_data and node_hash members are used
*
* \return TRUE if rule_data satisfies the expression, FALSE otherwise
*/
gboolean
pe__eval_attr_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data)
{
gboolean attr_allocated = FALSE;
const char *h_val = NULL;
const char *op = NULL;
const char *type = NULL;
const char *attr = NULL;
const char *value = NULL;
const char *value_source = NULL;
attr = crm_element_value(expr, XML_EXPR_ATTR_ATTRIBUTE);
op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION);
value = crm_element_value(expr, XML_EXPR_ATTR_VALUE);
type = crm_element_value(expr, XML_EXPR_ATTR_TYPE);
value_source = crm_element_value(expr, XML_EXPR_ATTR_VALUE_SOURCE);
if (attr == NULL) {
pe_err("Expression %s invalid: " XML_EXPR_ATTR_ATTRIBUTE
" not specified", pcmk__s(ID(expr), "without ID"));
return FALSE;
} else if (op == NULL) {
pe_err("Expression %s invalid: " XML_EXPR_ATTR_OPERATION
" not specified", pcmk__s(ID(expr), "without ID"));
}
if (rule_data->match_data != NULL) {
// Expand any regular expression submatches (%0-%9) in attribute name
if (rule_data->match_data->re != NULL) {
char *resolved_attr = pe_expand_re_matches(attr, rule_data->match_data->re);
if (resolved_attr != NULL) {
attr = (const char *) resolved_attr;
attr_allocated = TRUE;
}
}
// Get value appropriate to value-source
value = expand_value_source(value, value_source, rule_data->match_data);
}
if (rule_data->node_hash != NULL) {
h_val = (const char *)g_hash_table_lookup(rule_data->node_hash, attr);
}
if (attr_allocated) {
free((char *)attr);
attr = NULL;
}
return accept_attr_expr(h_val, value, type, op);
}
/*!
* \internal
* \brief Evaluate a date_expression
*
* \param[in] expr XML of rule expression
* \param[in] rule_data Only the now member is used
* \param[out] next_change If not NULL, set to when evaluation will change
*
* \return Standard Pacemaker return code
*/
int
pe__eval_date_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data,
crm_time_t *next_change)
{
crm_time_t *start = NULL;
crm_time_t *end = NULL;
const char *value = NULL;
const char *op = crm_element_value(expr, "operation");
xmlNode *duration_spec = NULL;
xmlNode *date_spec = NULL;
// "undetermined" will also be returned for parsing errors
int rc = pcmk_rc_undetermined;
crm_trace("Testing expression: %s", ID(expr));
duration_spec = first_named_child(expr, "duration");
date_spec = first_named_child(expr, "date_spec");
value = crm_element_value(expr, "start");
if (value != NULL) {
start = crm_time_new(value);
}
value = crm_element_value(expr, "end");
if (value != NULL) {
end = crm_time_new(value);
}
if (start != NULL && end == NULL && duration_spec != NULL) {
end = parse_xml_duration(start, duration_spec);
}
if (pcmk__str_eq(op, "in_range", pcmk__str_null_matches | pcmk__str_casei)) {
if ((start == NULL) && (end == NULL)) {
// in_range requires at least one of start or end
} else if ((start != NULL) && (crm_time_compare(rule_data->now, start) < 0)) {
rc = pcmk_rc_before_range;
crm_time_set_if_earlier(next_change, start);
} else if ((end != NULL) && (crm_time_compare(rule_data->now, end) > 0)) {
rc = pcmk_rc_after_range;
} else {
rc = pcmk_rc_within_range;
if (end && next_change) {
// Evaluation doesn't change until second after end
crm_time_add_seconds(end, 1);
crm_time_set_if_earlier(next_change, end);
}
}
} else if (pcmk__str_eq(op, "date_spec", pcmk__str_casei)) {
rc = pe_cron_range_satisfied(rule_data->now, date_spec);
// @TODO set next_change appropriately
} else if (pcmk__str_eq(op, "gt", pcmk__str_casei)) {
if (start == NULL) {
// gt requires start
} else if (crm_time_compare(rule_data->now, start) > 0) {
rc = pcmk_rc_within_range;
} else {
rc = pcmk_rc_before_range;
// Evaluation doesn't change until second after start
crm_time_add_seconds(start, 1);
crm_time_set_if_earlier(next_change, start);
}
} else if (pcmk__str_eq(op, "lt", pcmk__str_casei)) {
if (end == NULL) {
// lt requires end
} else if (crm_time_compare(rule_data->now, end) < 0) {
rc = pcmk_rc_within_range;
crm_time_set_if_earlier(next_change, end);
} else {
rc = pcmk_rc_after_range;
}
}
crm_time_free(start);
crm_time_free(end);
return rc;
}
gboolean
pe__eval_op_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data)
{
const char *name = crm_element_value(expr, XML_NVPAIR_ATTR_NAME);
const char *interval_s = crm_element_value(expr, XML_LRM_ATTR_INTERVAL);
guint interval;
crm_trace("Testing op_defaults expression: %s", ID(expr));
if (rule_data->op_data == NULL) {
crm_trace("No operations data provided");
return FALSE;
}
interval = crm_parse_interval_spec(interval_s);
if (interval == 0 && errno != 0) {
crm_trace("Could not parse interval: %s", interval_s);
return FALSE;
}
if (interval_s != NULL && interval != rule_data->op_data->interval) {
crm_trace("Interval doesn't match: %d != %d", interval, rule_data->op_data->interval);
return FALSE;
}
if (!pcmk__str_eq(name, rule_data->op_data->op_name, pcmk__str_none)) {
crm_trace("Name doesn't match: %s != %s", name, rule_data->op_data->op_name);
return FALSE;
}
return TRUE;
}
/*!
* \internal
* \brief Evaluate a node attribute expression based on #role
*
* \param[in] expr XML of rule expression
* \param[in] rule_data Only the role member is used
*
* \return TRUE if rule_data->role satisfies the expression, FALSE otherwise
*/
gboolean
pe__eval_role_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data)
{
gboolean accept = FALSE;
const char *op = NULL;
const char *value = NULL;
if (rule_data->role == RSC_ROLE_UNKNOWN) {
return accept;
}
value = crm_element_value(expr, XML_EXPR_ATTR_VALUE);
op = crm_element_value(expr, XML_EXPR_ATTR_OPERATION);
if (pcmk__str_eq(op, "defined", pcmk__str_casei)) {
if (rule_data->role > RSC_ROLE_STARTED) {
accept = TRUE;
}
} else if (pcmk__str_eq(op, "not_defined", pcmk__str_casei)) {
if ((rule_data->role > RSC_ROLE_UNKNOWN)
&& (rule_data->role < RSC_ROLE_UNPROMOTED)) {
accept = TRUE;
}
} else if (pcmk__str_eq(op, "eq", pcmk__str_casei)) {
if (text2role(value) == rule_data->role) {
accept = TRUE;
}
} else if (pcmk__str_eq(op, "ne", pcmk__str_casei)) {
// Test "ne" only with promotable clone roles
if ((rule_data->role > RSC_ROLE_UNKNOWN)
&& (rule_data->role < RSC_ROLE_UNPROMOTED)) {
accept = FALSE;
} else if (text2role(value) != rule_data->role) {
accept = TRUE;
}
}
return accept;
}
gboolean
pe__eval_rsc_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data)
{
const char *class = crm_element_value(expr, XML_AGENT_ATTR_CLASS);
const char *provider = crm_element_value(expr, XML_AGENT_ATTR_PROVIDER);
const char *type = crm_element_value(expr, XML_EXPR_ATTR_TYPE);
crm_trace("Testing rsc_defaults expression: %s", ID(expr));
if (rule_data->rsc_data == NULL) {
crm_trace("No resource data provided");
return FALSE;
}
if (class != NULL &&
!pcmk__str_eq(class, rule_data->rsc_data->standard, pcmk__str_none)) {
crm_trace("Class doesn't match: %s != %s", class, rule_data->rsc_data->standard);
return FALSE;
}
if ((provider == NULL && rule_data->rsc_data->provider != NULL) ||
(provider != NULL && rule_data->rsc_data->provider == NULL) ||
!pcmk__str_eq(provider, rule_data->rsc_data->provider, pcmk__str_none)) {
crm_trace("Provider doesn't match: %s != %s", provider, rule_data->rsc_data->provider);
return FALSE;
}
if (type != NULL &&
!pcmk__str_eq(type, rule_data->rsc_data->agent, pcmk__str_none)) {
crm_trace("Agent doesn't match: %s != %s", type, rule_data->rsc_data->agent);
return FALSE;
}
return TRUE;
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include
gboolean
test_ruleset(xmlNode *ruleset, GHashTable *node_hash, crm_time_t *now)
{
return pe_evaluate_rules(ruleset, node_hash, now, NULL);
}
gboolean
test_rule(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now)
{
return pe_test_rule(rule, node_hash, role, now, NULL, NULL);
}
gboolean
pe_test_rule_re(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now, pe_re_match_data_t * re_match_data)
{
pe_match_data_t match_data = {
.re = re_match_data,
.params = NULL,
.meta = NULL,
};
return pe_test_rule(rule, node_hash, role, now, NULL, &match_data);
}
gboolean
pe_test_rule_full(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role,
crm_time_t *now, pe_match_data_t *match_data)
{
return pe_test_rule(rule, node_hash, role, now, NULL, match_data);
}
gboolean
test_expression(xmlNode * expr, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now)
{
return pe_test_expression(expr, node_hash, role, now, NULL, NULL);
}
gboolean
pe_test_expression_re(xmlNode * expr, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now, pe_re_match_data_t * re_match_data)
{
pe_match_data_t match_data = {
.re = re_match_data,
.params = NULL,
.meta = NULL,
};
return pe_test_expression(expr, node_hash, role, now, NULL, &match_data);
}
gboolean
pe_test_expression_full(xmlNode *expr, GHashTable *node_hash,
enum rsc_role_e role, crm_time_t *now,
pe_match_data_t *match_data)
{
return pe_test_expression(expr, node_hash, role, now, NULL, match_data);
}
void
unpack_instance_attributes(xmlNode *top, xmlNode *xml_obj, const char *set_name,
GHashTable *node_hash, GHashTable *hash,
const char *always_first, gboolean overwrite,
crm_time_t *now)
{
pe_rule_eval_data_t rule_data = {
.node_hash = node_hash,
.role = RSC_ROLE_UNKNOWN,
.now = now,
.match_data = NULL,
.rsc_data = NULL,
.op_data = NULL
};
pe_eval_nvpairs(top, xml_obj, set_name, &rule_data, hash, always_first,
overwrite, NULL);
}
// LCOV_EXCL_STOP
// End deprecated API