Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/include/crm/common/results.h b/include/crm/common/results.h
index a2d35ddf6b..6897527c0d 100644
--- a/include/crm/common/results.h
+++ b/include/crm/common/results.h
@@ -1,419 +1,425 @@
/*
* Copyright 2012-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__CRM_COMMON_RESULTS__H
#define PCMK__CRM_COMMON_RESULTS__H
+#include <glib.h> // gboolean
+
#ifdef __cplusplus
extern "C" {
#endif
/*!
* \file
* \brief Function and executable result codes
* \ingroup core
*/
// Lifted from config.h
/* The _Noreturn keyword of C11. */
#ifndef _Noreturn
# if (defined __cplusplus \
&& ((201103 <= __cplusplus && !(__GNUC__ == 4 && __GNUC_MINOR__ == 7)) \
|| (defined _MSC_VER && 1900 <= _MSC_VER)))
# define _Noreturn [[noreturn]]
# elif ((!defined __cplusplus || defined __clang__) \
&& (201112 <= (defined __STDC_VERSION__ ? __STDC_VERSION__ : 0) \
|| 4 < __GNUC__ + (7 <= __GNUC_MINOR__)))
/* _Noreturn works as-is. */
# elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__) || 0x5110 <= __SUNPRO_C
# define _Noreturn __attribute__ ((__noreturn__))
# elif 1200 <= (defined _MSC_VER ? _MSC_VER : 0)
# define _Noreturn __declspec (noreturn)
# else
# define _Noreturn
# endif
#endif
#define CRM_ASSERT(expr) do { \
if (!(expr)) { \
crm_abort(__FILE__, __func__, __LINE__, #expr, TRUE, FALSE); \
} \
} 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)
// NOTE: sbd (as of at least 1.5.2) uses this
#define pcmk_ok 0
#define PCMK_ERROR_OFFSET 190 /* Replacements on non-linux systems, see include/portability.h */
#define PCMK_CUSTOM_OFFSET 200 /* Purely custom codes */
#define pcmk_err_generic 201
#define pcmk_err_no_quorum 202
#define pcmk_err_schema_validation 203
#define pcmk_err_transform_failed 204
#define pcmk_err_old_data 205
// NOTE: sbd (as of at least 1.5.2) uses this
#define pcmk_err_diff_failed 206
// NOTE: sbd (as of at least 1.5.2) uses this
#define pcmk_err_diff_resync 207
#define pcmk_err_cib_modified 208
#define pcmk_err_cib_backup 209
#define pcmk_err_cib_save 210
#define pcmk_err_schema_unchanged 211
#define pcmk_err_cib_corrupt 212
#define pcmk_err_multiple 213
#define pcmk_err_node_unknown 214
#define pcmk_err_already 215
/* On HPPA 215 is ENOSYM (Unknown error 215), which hopefully never happens. */
#ifdef __hppa__
#define pcmk_err_bad_nvpair 250 /* 216 is ENOTSOCK */
#define pcmk_err_unknown_format 252 /* 217 is EDESTADDRREQ */
#else
#define pcmk_err_bad_nvpair 216
#define pcmk_err_unknown_format 217
#endif
/*!
* \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_compression = -1039,
pcmk_rc_ns_resolution = -1038,
pcmk_rc_no_transaction = -1037,
pcmk_rc_bad_xml_patch = -1036,
pcmk_rc_bad_input = -1035,
pcmk_rc_disabled = -1034,
pcmk_rc_duplicate_id = -1033,
pcmk_rc_unpack_error = -1032,
pcmk_rc_invalid_transition = -1031,
pcmk_rc_graph_error = -1030,
pcmk_rc_dot_error = -1029,
pcmk_rc_underflow = -1028,
pcmk_rc_no_input = -1027,
pcmk_rc_no_output = -1026,
pcmk_rc_after_range = -1025,
pcmk_rc_within_range = -1024,
pcmk_rc_before_range = -1023,
pcmk_rc_undetermined = -1022,
pcmk_rc_op_unsatisfied = -1021,
pcmk_rc_ipc_pid_only = -1020,
pcmk_rc_ipc_unresponsive = -1019,
pcmk_rc_ipc_unauthorized = -1018,
pcmk_rc_no_quorum = -1017,
pcmk_rc_schema_validation = -1016,
pcmk_rc_schema_unchanged = -1015,
pcmk_rc_transform_failed = -1014,
pcmk_rc_old_data = -1013,
pcmk_rc_diff_failed = -1012,
pcmk_rc_diff_resync = -1011,
pcmk_rc_cib_modified = -1010,
pcmk_rc_cib_backup = -1009,
pcmk_rc_cib_save = -1008,
pcmk_rc_cib_corrupt = -1007,
pcmk_rc_multiple = -1006,
pcmk_rc_node_unknown = -1005,
pcmk_rc_already = -1004,
pcmk_rc_bad_nvpair = -1003,
pcmk_rc_unknown_format = -1002,
// Developers: Use a more specific code than pcmk_rc_error whenever possible
pcmk_rc_error = -1001,
// Values -1 through -1000 reserved for caller use
// NOTE: sbd (as of at least 1.5.2) uses this
pcmk_rc_ok = 0
// Positive values reserved for system error numbers
};
/*!
* \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
// NOTE: booth (as of at least 1.1) uses this value
PCMK_OCF_UNKNOWN_ERROR = 1, //!< Unspecified error
PCMK_OCF_INVALID_PARAM = 2, //!< Parameter invalid (in local context)
PCMK_OCF_UNIMPLEMENT_FEATURE = 3, //!< Requested action not implemented
PCMK_OCF_INSUFFICIENT_PRIV = 4, //!< Insufficient privileges
PCMK_OCF_NOT_INSTALLED = 5, //!< Dependencies not available locally
PCMK_OCF_NOT_CONFIGURED = 6, //!< Parameter invalid (inherently)
// NOTE: booth (as of at least 1.1) uses this value
PCMK_OCF_NOT_RUNNING = 7, //!< Service safely stopped
PCMK_OCF_RUNNING_PROMOTED = 8, //!< Service active and promoted
PCMK_OCF_FAILED_PROMOTED = 9, //!< Service failed and possibly in promoted role
PCMK_OCF_DEGRADED = 190, //!< Service active but more likely to fail soon
PCMK_OCF_DEGRADED_PROMOTED = 191, //!< Service promoted but more likely to fail soon
/* These two are Pacemaker extensions, not in the OCF standard. The
* controller records PCMK_OCF_UNKNOWN for pending actions.
* PCMK_OCF_CONNECTION_DIED is used only with older DCs that don't support
* PCMK_EXEC_NOT_CONNECTED.
*/
PCMK_OCF_CONNECTION_DIED = 189, //!< \deprecated See PCMK_EXEC_NOT_CONNECTED
PCMK_OCF_UNKNOWN = 193, //!< Action is pending
#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
};
// NOTE: sbd (as of at least 1.5.2) uses this
/*!
* \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);
// NOTE: sbd (as of at least 1.5.2) uses this
const char *pcmk_rc_str(int rc);
crm_exit_t pcmk_rc2exitc(int rc);
enum ocf_exitcode pcmk_rc2ocf(int rc);
int pcmk_rc2legacy(int rc);
int pcmk_legacy2rc(int legacy_rc);
// NOTE: sbd (as of at least 1.5.2) uses this
const char *pcmk_strerror(int rc);
const char *pcmk_errorname(int rc);
const char *crm_exit_name(crm_exit_t exit_code);
// NOTE: sbd (as of at least 1.5.2) uses this
const char *crm_exit_str(crm_exit_t exit_code);
_Noreturn crm_exit_t crm_exit(crm_exit_t rc);
+/* coverity[+kill] */
+void crm_abort(const char *file, const char *function, int line,
+ const char *condition, gboolean do_core, gboolean do_fork);
+
static inline const char *
pcmk_exec_status_str(enum pcmk_exec_status status)
{
switch (status) {
case PCMK_EXEC_PENDING: return "pending";
case PCMK_EXEC_DONE: return "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/include/crm/common/util.h b/include/crm/common/util.h
index 9247a57388..437768c168 100644
--- a/include/crm/common/util.h
+++ b/include/crm/common/util.h
@@ -1,125 +1,121 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#ifndef PCMK__CRM_COMMON_UTIL__H
#define PCMK__CRM_COMMON_UTIL__H
#include <sys/types.h> // gid_t, mode_t, size_t, time_t, uid_t
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h> // uint32_t
#include <limits.h>
#include <signal.h>
#include <glib.h>
#include <crm/common/acl.h>
#include <crm/common/actions.h>
#include <crm/common/agents.h>
#include <crm/common/results.h>
#include <crm/common/scores.h>
#include <crm/common/nvpair.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file
* \brief Utility functions
* \ingroup core
*/
/* public node attribute functions (from attrd_client.c) */
char *pcmk_promotion_score_name(const char *rsc_id);
/* public Pacemaker Remote functions (from remote.c) */
int crm_default_remote_port(void);
/* public string functions (from strings.c) */
// NOTE: sbd (as of at least 1.5.2) uses this
gboolean crm_is_true(const char *s);
int crm_str_to_boolean(const char *s, int *ret);
// NOTE: sbd (as of at least 1.5.2) uses this
long long crm_get_msec(const char *input);
// NOTE: sbd (as of at least 1.5.2) uses this
char *crm_strdup_printf(char const *format, ...) G_GNUC_PRINTF(1, 2);
int pcmk_parse_interval_spec(const char *input, guint *result_ms);
int compare_version(const char *version1, const char *version2);
-/* coverity[+kill] */
-void crm_abort(const char *file, const char *function, int line,
- const char *condition, gboolean do_core, gboolean do_fork);
-
/*!
* \brief Check whether any of specified flags are set in a flag group
*
* \param[in] flag_group The flag group being examined
* \param[in] flags_to_check Which flags in flag_group should be checked
*
* \return true if \p flags_to_check is nonzero and any of its flags are set in
* \p flag_group, or false otherwise
*/
static inline bool
pcmk_any_flags_set(uint64_t flag_group, uint64_t flags_to_check)
{
return (flag_group & flags_to_check) != 0;
}
/*!
* \brief Check whether all of specified flags are set in a flag group
*
* \param[in] flag_group The flag group being examined
* \param[in] flags_to_check Which flags in flag_group should be checked
*
* \return true if \p flags_to_check is zero or all of its flags are set in
* \p flag_group, or false otherwise
*/
static inline bool
pcmk_all_flags_set(uint64_t flag_group, uint64_t flags_to_check)
{
return (flag_group & flags_to_check) == flags_to_check;
}
/*!
* \brief Convenience alias for pcmk_all_flags_set(), to check single flag
*/
#define pcmk_is_set(g, f) pcmk_all_flags_set((g), (f))
char *crm_md5sum(const char *buffer);
char *crm_generate_uuid(void);
// This belongs in ipc.h but is here for backward compatibility
bool crm_is_daemon_name(const char *name);
int crm_user_lookup(const char *name, uid_t * uid, gid_t * gid);
int pcmk_daemon_user(uid_t *uid, gid_t *gid);
#ifdef HAVE_GNUTLS_GNUTLS_H
void crm_gnutls_global_init(void);
#endif
bool pcmk_str_is_infinity(const char *s);
bool pcmk_str_is_minus_infinity(const char *s);
#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1)
#include <crm/common/util_compat.h>
#endif
#ifdef __cplusplus
}
#endif
#endif
diff --git a/lib/common/results.c b/lib/common/results.c
index 396ac0d2f3..33afb0980e 100644
--- a/lib/common/results.c
+++ b/lib/common/results.c
@@ -1,1144 +1,1256 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <bzlib.h>
#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
#include <qb/qbdefs.h>
#include <crm/common/mainloop.h>
#include <crm/common/xml.h>
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,
},
{ "pcmk_rc_no_transaction",
"No active transaction found",
-pcmk_err_generic,
},
{ "pcmk_rc_ns_resolution",
"Nameserver resolution error",
-pcmk_err_generic,
},
{ "pcmk_rc_compression",
"Compression/decompression error",
-pcmk_err_generic,
},
};
/*!
* \internal
* \brief The number of <tt>enum pcmk_rc_e</tt> 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:
case pcmk_rc_compression:
return CRM_EX_SOFTWARE;
case EBADMSG:
case EMSGSIZE:
case ENOMSG:
case ENOPROTOOPT:
case EPROTO:
case EPROTONOSUPPORT:
case EPROTOTYPE:
return CRM_EX_PROTOCOL;
case ECOMM:
case ENOMEM:
return CRM_EX_OSERR;
case ECONNABORTED:
case ECONNREFUSED:
case ECONNRESET:
case ENOTCONN:
return CRM_EX_DISCONNECT;
case EEXIST:
case pcmk_rc_already:
return CRM_EX_EXISTS;
case EIO:
case pcmk_rc_dot_error:
case pcmk_rc_graph_error:
return CRM_EX_IOERR;
case ENOTSUP:
#if EOPNOTSUPP != ENOTSUP
case EOPNOTSUPP:
#endif
return CRM_EX_UNIMPLEMENT_FEATURE;
case ENOTUNIQ:
case pcmk_rc_multiple:
return CRM_EX_MULTIPLE;
case ENODEV:
case ENOENT:
case ENXIO:
case pcmk_rc_no_transaction:
case pcmk_rc_unknown_format:
return CRM_EX_NOSUCH;
case pcmk_rc_node_unknown:
case pcmk_rc_ns_resolution:
return CRM_EX_NOHOST;
case ETIME:
case ETIMEDOUT:
return CRM_EX_TIMEOUT;
case EAGAIN:
case EBUSY:
return CRM_EX_UNSATISFIED;
case pcmk_rc_before_range:
return CRM_EX_NOT_YET_IN_EFFECT;
case pcmk_rc_after_range:
return CRM_EX_EXPIRED;
case pcmk_rc_undetermined:
return CRM_EX_INDETERMINATE;
case pcmk_rc_op_unsatisfied:
return CRM_EX_UNSATISFIED;
case pcmk_rc_within_range:
return CRM_EX_OK;
case pcmk_rc_no_input:
return CRM_EX_NOINPUT;
case pcmk_rc_duplicate_id:
return CRM_EX_MULTIPLE;
case pcmk_rc_bad_input:
case pcmk_rc_bad_xml_patch:
return CRM_EX_DATAERR;
default:
return CRM_EX_ERROR;
}
}
/*!
* \brief Map a function return code to the most similar OCF exit code
*
* \param[in] rc Function return code
*
* \return Most similar OCF exit code
*/
enum ocf_exitcode
pcmk_rc2ocf(int rc)
{
switch (rc) {
case pcmk_rc_ok:
return PCMK_OCF_OK;
case pcmk_rc_bad_nvpair:
return PCMK_OCF_INVALID_PARAM;
case EACCES:
return PCMK_OCF_INSUFFICIENT_PRIV;
case ENOTSUP:
#if EOPNOTSUPP != ENOTSUP
case EOPNOTSUPP:
#endif
return PCMK_OCF_UNIMPLEMENT_FEATURE;
default:
return PCMK_OCF_UNKNOWN_ERROR;
}
}
// Other functions
/*!
* \brief Map a getaddrinfo() return code to the most similar Pacemaker
* return code
*
* \param[in] gai getaddrinfo() return code
*
* \return Most similar Pacemaker return code
*/
int
pcmk__gaierror2rc(int gai)
{
switch (gai) {
case 0:
return pcmk_rc_ok;
case EAI_AGAIN:
return EAGAIN;
case EAI_BADFLAGS:
case EAI_SERVICE:
return EINVAL;
case EAI_FAMILY:
return EAFNOSUPPORT;
case EAI_MEMORY:
return ENOMEM;
case EAI_NONAME:
return pcmk_rc_node_unknown;
case EAI_SOCKTYPE:
return ESOCKTNOSUPPORT;
case EAI_SYSTEM:
return errno;
default:
return pcmk_rc_ns_resolution;
}
}
/*!
* \brief Map a bz2 return code to the most similar Pacemaker return code
*
* \param[in] bz2 bz2 return code
*
* \return Most similar Pacemaker return code
*/
int
pcmk__bzlib2rc(int bz2)
{
switch (bz2) {
case BZ_OK:
case BZ_RUN_OK:
case BZ_FLUSH_OK:
case BZ_FINISH_OK:
case BZ_STREAM_END:
return pcmk_rc_ok;
case BZ_MEM_ERROR:
return ENOMEM;
case BZ_DATA_ERROR:
case BZ_DATA_ERROR_MAGIC:
case BZ_UNEXPECTED_EOF:
return pcmk_rc_bad_input;
case BZ_IO_ERROR:
return EIO;
case BZ_OUTBUFF_FULL:
return EFBIG;
default:
return pcmk_rc_compression;
}
}
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;
dst->exit_reason = pcmk__str_copy(src->exit_reason);
dst->action_stdout = pcmk__str_copy(src->action_stdout);
dst->action_stderr = pcmk__str_copy(src->action_stderr);
}
+/*!
+ * \internal
+ * \brief Log a failed assertion
+ *
+ * \param[in] file File making the assertion
+ * \param[in] function Function making the assertion
+ * \param[in] line Line of file making the assertion
+ * \param[in] assert_condition String representation of assertion
+ */
+static void
+log_assertion_as(const char *file, const char *function, int line,
+ const char *assert_condition)
+{
+ if (!pcmk__is_daemon) {
+ crm_enable_stderr(TRUE); // Make sure command-line user sees message
+ }
+ crm_err("%s: Triggered fatal assertion at %s:%d : %s",
+ function, file, line, assert_condition);
+}
+
+/* coverity[+kill] */
+/*!
+ * \internal
+ * \brief Log a failed assertion and abort
+ *
+ * \param[in] file File making the assertion
+ * \param[in] function Function making the assertion
+ * \param[in] line Line of file making the assertion
+ * \param[in] assert_condition String representation of assertion
+ *
+ * \note This does not return
+ */
+static _Noreturn void
+abort_as(const char *file, const char *function, int line,
+ const char *assert_condition)
+{
+ log_assertion_as(file, function, line, assert_condition);
+ abort();
+}
+
+/* coverity[+kill] */
+/*!
+ * \internal
+ * \brief Handle a failed assertion
+ *
+ * When called by a daemon, fork a child that aborts (to dump core), otherwise
+ * abort the current process.
+ *
+ * \param[in] file File making the assertion
+ * \param[in] function Function making the assertion
+ * \param[in] line Line of file making the assertion
+ * \param[in] assert_condition String representation of assertion
+ */
+static void
+fail_assert_as(const char *file, const char *function, int line,
+ const char *assert_condition)
+{
+ int status = 0;
+ pid_t pid = 0;
+
+ if (!pcmk__is_daemon) {
+ abort_as(file, function, line, assert_condition); // does not return
+ }
+
+ pid = fork();
+ switch (pid) {
+ case -1: // Fork failed
+ crm_warn("%s: Cannot dump core for non-fatal assertion at %s:%d "
+ ": %s", function, file, line, assert_condition);
+ break;
+
+ case 0: // Child process: just abort to dump core
+ abort();
+ break;
+
+ default: // Parent process: wait for child
+ crm_err("%s: Forked child [%d] to record non-fatal assertion at "
+ "%s:%d : %s", function, pid, file, line, assert_condition);
+ crm_write_blackbox(SIGTRAP, NULL);
+ do {
+ if (waitpid(pid, &status, 0) == pid) {
+ return; // Child finished dumping core
+ }
+ } while (errno == EINTR);
+ if (errno == ECHILD) {
+ // crm_mon ignores SIGCHLD
+ crm_trace("Cannot wait on forked child [%d] "
+ "(SIGCHLD is probably ignored)", pid);
+ } else {
+ crm_err("Cannot wait on forked child [%d]: %s",
+ pid, pcmk_rc_str(errno));
+ }
+ break;
+ }
+}
+
+/* coverity[+kill] */
+void
+crm_abort(const char *file, const char *function, int line,
+ const char *assert_condition, gboolean do_core, gboolean do_fork)
+{
+ if (!do_fork) {
+ abort_as(file, function, line, assert_condition);
+ } else if (do_core) {
+ fail_assert_as(file, function, line, assert_condition);
+ } else {
+ log_assertion_as(file, function, line, assert_condition);
+ }
+}
+
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/common/results_compat.h>
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_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 f1b6047700..4e44878b6e 100644
--- a/lib/common/strings.c
+++ b/lib/common/strings.c
@@ -1,1455 +1,1454 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
-#include "crm/common/results.h"
#include <crm_internal.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <regex.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <float.h> // DBL_MIN
#include <limits.h>
#include <bzlib.h>
#include <sys/types.h>
/*!
* \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_debug("Integer parsed from '%s' was clipped to %lld",
text, local_result);
} else if (local_end_text == text) {
rc = EINVAL;
local_result = default_value;
crm_debug("Could not parse integer from '%s' (using %lld instead): "
"No digits found", text, default_value);
} else if (errno != 0) {
rc = errno;
local_result = default_value;
crm_debug("Could not parse integer from '%s' (using %lld instead): "
"%s", text, default_value, pcmk_rc_str(rc));
}
if ((end_text == NULL) && !pcmk__str_empty(local_end_text)) {
crm_debug("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 (rc != pcmk_rc_ok) {
crm_warn("'%s' is not a valid port: %s", text, pcmk_rc_str(rc));
} else if ((text != NULL) // 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) {
crm_warn("Using default (%u) for %s because '%s' is not a "
"valid integer: %s", default_val, key, value, pcmk_rc_str(rc));
return rc;
}
if ((value_ll < 0) || (value_ll > G_MAXUINT)) {
crm_warn("Using default (%u) for %s because '%s' is not in valid range",
default_val, key, value);
return ERANGE;
}
if (result != NULL) {
*result = (guint) value_ll;
}
return pcmk_rc_ok;
}
/*!
* \brief Parse a time+units string and return milliseconds equivalent
*
* \param[in] input String with a nonnegative 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
* \c PCMK__PARSE_INT_DEFAULT on error
*/
long long
crm_get_msec(const char *input)
{
char *units = NULL; // Do not free; will point to part of input
long long multiplier = 1000;
long long divisor = 1;
long long msec = PCMK__PARSE_INT_DEFAULT;
int rc = pcmk_rc_ok;
if (input == NULL) {
return PCMK__PARSE_INT_DEFAULT;
}
// Skip initial whitespace
while (isspace(*input)) {
input++;
}
rc = scan_ll(input, &msec, PCMK__PARSE_INT_DEFAULT, &units);
if ((rc == EOVERFLOW) && (msec > 0)) {
crm_warn("'%s' will be clipped to %lld", input, msec);
} else if ((rc != pcmk_rc_ok) || (msec < 0)) {
crm_warn("'%s' is not a valid time duration: %s",
input, ((rc == pcmk_rc_ok)? "Negative" : pcmk_rc_str(rc)));
return PCMK__PARSE_INT_DEFAULT;
}
/* If the number is a decimal, scan_ll() reads only the integer part. Skip
* any remaining digits or decimal characters.
*
* @COMPAT Well-formed and malformed decimals are both accepted inputs. For
* example, "3.14 ms" and "3.1.4 ms" are treated the same as "3ms" and
* parsed successfully. At a compatibility break, decide if this is still
* desired.
*/
while (isdigit(*units) || (*units == '.')) {
units++;
}
// Skip any additional whitespace after the number
while (isspace(*units)) {
units++;
}
/* @COMPAT Use exact comparisons. Currently, we match too liberally, and the
* second strncasecmp() in each case is redundant.
*/
if ((*units == '\0')
|| (strncasecmp(units, "s", 1) == 0)
|| (strncasecmp(units, "sec", 3) == 0)) {
multiplier = 1000;
divisor = 1;
} else if ((strncasecmp(units, "ms", 2) == 0)
|| (strncasecmp(units, "msec", 4) == 0)) {
multiplier = 1;
divisor = 1;
} else if ((strncasecmp(units, "us", 2) == 0)
|| (strncasecmp(units, "usec", 4) == 0)) {
multiplier = 1;
divisor = 1000;
} else if ((strncasecmp(units, "m", 1) == 0)
|| (strncasecmp(units, "min", 3) == 0)) {
multiplier = 60 * 1000;
divisor = 1;
} else if ((strncasecmp(units, "h", 1) == 0)
|| (strncasecmp(units, "hr", 2) == 0)) {
multiplier = 60 * 60 * 1000;
divisor = 1;
} else {
// Invalid units
return PCMK__PARSE_INT_DEFAULT;
}
// Apply units, capping at LLONG_MAX
if (msec > (LLONG_MAX / multiplier)) {
return LLONG_MAX;
}
return (msec * multiplier) / divisor;
}
/*!
* \brief Parse milliseconds from a Pacemaker interval specification
*
* \param[in] input Pacemaker time interval specification (a bare number
* of seconds; a number with a unit, optionally with
* whitespace before and/or after the number; or an ISO
* 8601 duration)
* \param[out] result_ms Where to store milliseconds equivalent of \p input on
* success (limited to the range of an unsigned integer),
* or 0 if \p input is \c NULL or invalid
*
* \return Standard Pacemaker return code (specifically, \c pcmk_rc_ok if
* \p input is valid or \c NULL, and \c EINVAL otherwise)
*/
int
pcmk_parse_interval_spec(const char *input, guint *result_ms)
{
long long msec = PCMK__PARSE_INT_DEFAULT;
int rc = pcmk_rc_ok;
if (input == NULL) {
msec = 0;
goto done;
}
if (input[0] == 'P') {
crm_time_t *period_s = crm_time_parse_duration(input);
if (period_s != NULL) {
msec = crm_time_get_seconds(period_s);
msec = QB_MIN(msec, G_MAXUINT / 1000) * 1000;
crm_time_free(period_s);
}
} else {
msec = crm_get_msec(input);
}
if (msec == PCMK__PARSE_INT_DEFAULT) {
crm_warn("Using 0 instead of invalid interval specification '%s'",
input);
msec = 0;
rc = EINVAL;
}
done:
if (result_ms != NULL) {
*result_ms = (msec >= G_MAXUINT)? G_MAXUINT : (guint) msec;
}
return rc;
}
gboolean
crm_is_true(const char *s)
{
gboolean ret = FALSE;
return (crm_str_to_boolean(s, &ret) < 0)? FALSE : ret;
}
int
crm_str_to_boolean(const char *s, int *ret)
{
if (s == NULL) {
return -1;
}
if (pcmk__strcase_any_of(s, PCMK_VALUE_TRUE, "on", "yes", "y", "1", NULL)) {
if (ret != NULL) {
*ret = TRUE;
}
return 1;
}
if (pcmk__strcase_any_of(s, PCMK_VALUE_FALSE, "off", "no", "n", "0",
NULL)) {
if (ret != NULL) {
*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);
}
/*!
* \internal
* \brief Insert string copies into a hash table as key and value
*
* \param[in,out] table Hash table to add to
* \param[in] name String to add a copy of as key
* \param[in] value String to add a copy of as value
*
* \note This asserts on invalid arguments or memory allocation failure.
*/
void
pcmk__insert_dup(GHashTable *table, const char *name, const char *value)
{
CRM_ASSERT((table != NULL) && (name != NULL));
g_hash_table_insert(table, pcmk__str_copy(name), pcmk__str_copy(value));
}
/* 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) {
pcmk__insert_dup((GHashTable *) user_data,
(const char *) key, (const char *) 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 = pcmk__assert_alloc((size_t) max, sizeof(char));
*result_len = max;
rc = BZ2_bzBuffToBuffCompress(compressed, result_len, uncompressed, length,
CRM_BZ2_BLOCKS, 0, CRM_BZ2_WORK);
rc = pcmk__bzlib2rc(rc);
free(uncompressed);
if (rc != pcmk_rc_ok) {
crm_err("Compression of %d bytes failed: %s " CRM_XS " rc=%d",
length, pcmk_rc_str(rc), rc);
free(compressed);
return rc;
}
#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_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_bad_input;
} else {
return pcmk_rc_ok;
}
}
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) {
return rc;
} else if (*more_remainder != '\0') {
return pcmk_rc_bad_input;
}
}
} else if (*remainder && *remainder != '-') {
*start = PCMK__PARSE_INT_DEFAULT;
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 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-<tt>NULL</tt> 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 Copy a string, asserting on failure
*
* \param[in] file File where \p function is located
* \param[in] function Calling function
* \param[in] line Line within \p file
* \param[in] str String to copy (can be \c NULL)
*
* \return Newly allocated copy of \p str, or \c NULL if \p str is \c NULL
*
* \note The caller is responsible for freeing the return value using \c free().
*/
char *
pcmk__str_copy_as(const char *file, const char *function, uint32_t line,
const char *str)
{
if (str != NULL) {
char *result = strdup(str);
if (result == NULL) {
crm_abort(file, function, line, "Out of memory", FALSE, TRUE);
crm_exit(CRM_EX_OSERR);
}
return result;
}
return NULL;
}
/*!
* \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);
*str = pcmk__str_copy(value);
}
}
/*!
* \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 <tt>NULL</tt>-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 <crm/common/util_compat.h>
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;
}
}
(void) 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/strings/pcmk__parse_ll_range_test.c b/lib/common/tests/strings/pcmk__parse_ll_range_test.c
index 7656ad7e3b..41b8d2fd5c 100644
--- a/lib/common/tests/strings/pcmk__parse_ll_range_test.c
+++ b/lib/common/tests/strings/pcmk__parse_ll_range_test.c
@@ -1,117 +1,116 @@
/*
- * Copyright 2020-2023 the Pacemaker project contributors
+ * Copyright 2020-2024 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 <crm_internal.h>
#include <crm/common/unittest_internal.h>
static void
empty_input_string(void **state)
{
long long start, end;
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_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_bad_input);
}
static void
garbage(void **state)
{
long long start, end;
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_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), 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/common/utils.c b/lib/common/utils.c
index e8d343ea01..001d420069 100644
--- a/lib/common/utils.c
+++ b/lib/common/utils.c
@@ -1,549 +1,437 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
-#include <sys/types.h>
-#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/utsname.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <pwd.h>
#include <time.h>
#include <libgen.h>
#include <signal.h>
#include <grp.h>
#include <qb/qbdefs.h>
#include <crm/crm.h>
#include <crm/services.h>
#include <crm/cib/internal.h>
#include <crm/common/xml.h>
#include <crm/common/util.h>
#include <crm/common/ipc.h>
#include <crm/common/iso8601.h>
#include <crm/common/mainloop.h>
#include <libxml2/libxml/relaxng.h>
#include "crmcommon_private.h"
CRM_TRACE_INIT_DATA(common);
gboolean crm_config_error = FALSE;
gboolean crm_config_warning = FALSE;
char *crm_system_name = NULL;
bool
pcmk__is_user_in_group(const char *user, const char *group)
{
struct group *grent;
char **gr_mem;
if (user == NULL || group == NULL) {
return false;
}
setgrent();
while ((grent = getgrent()) != NULL) {
if (grent->gr_mem == NULL) {
continue;
}
if(strcmp(group, grent->gr_name) != 0) {
continue;
}
gr_mem = grent->gr_mem;
while (*gr_mem != NULL) {
if (!strcmp(user, *gr_mem++)) {
endgrent();
return true;
}
}
}
endgrent();
return false;
}
int
crm_user_lookup(const char *name, uid_t * uid, gid_t * gid)
{
int rc = pcmk_ok;
char *buffer = NULL;
struct passwd pwd;
struct passwd *pwentry = NULL;
buffer = calloc(1, PCMK__PW_BUFFER_LEN);
if (buffer == NULL) {
return -ENOMEM;
}
rc = getpwnam_r(name, &pwd, buffer, PCMK__PW_BUFFER_LEN, &pwentry);
if (pwentry) {
if (uid) {
*uid = pwentry->pw_uid;
}
if (gid) {
*gid = pwentry->pw_gid;
}
crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid);
} else {
rc = rc? -rc : -EINVAL;
crm_info("User %s lookup: %s", name, pcmk_strerror(rc));
}
free(buffer);
return rc;
}
/*!
* \brief Get user and group IDs of pacemaker daemon user
*
* \param[out] uid If non-NULL, where to store daemon user ID
* \param[out] gid If non-NULL, where to store daemon group ID
*
* \return pcmk_ok on success, -errno otherwise
*/
int
pcmk_daemon_user(uid_t *uid, gid_t *gid)
{
static uid_t daemon_uid;
static gid_t daemon_gid;
static bool found = false;
int rc = pcmk_ok;
if (!found) {
rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid);
if (rc == pcmk_ok) {
found = true;
}
}
if (found) {
if (uid) {
*uid = daemon_uid;
}
if (gid) {
*gid = daemon_gid;
}
}
return rc;
}
/*!
* \internal
* \brief Return the integer equivalent of a portion of a string
*
* \param[in] text Pointer to beginning of string portion
* \param[out] end_text This will point to next character after integer
*/
static int
version_helper(const char *text, const char **end_text)
{
int atoi_result = -1;
CRM_ASSERT(end_text != NULL);
errno = 0;
if (text != NULL && text[0] != 0) {
/* seemingly sacrificing const-correctness -- because while strtol
doesn't modify the input, it doesn't want to artificially taint the
"end_text" pointer-to-pointer-to-first-char-in-string with constness
in case the input wasn't actually constant -- by semantic definition
not a single character will get modified so it shall be perfectly
safe to make compiler happy with dropping "const" qualifier here */
atoi_result = (int) strtol(text, (char **) end_text, 10);
if (errno == EINVAL) {
crm_err("Conversion of '%s' %c failed", text, text[0]);
atoi_result = -1;
}
}
return atoi_result;
}
/*
* version1 < version2 : -1
* version1 = version2 : 0
* version1 > version2 : 1
*/
int
compare_version(const char *version1, const char *version2)
{
int rc = 0;
int lpc = 0;
const char *ver1_iter, *ver2_iter;
if (version1 == NULL && version2 == NULL) {
return 0;
} else if (version1 == NULL) {
return -1;
} else if (version2 == NULL) {
return 1;
}
ver1_iter = version1;
ver2_iter = version2;
while (1) {
int digit1 = 0;
int digit2 = 0;
lpc++;
if (ver1_iter == ver2_iter) {
break;
}
if (ver1_iter != NULL) {
digit1 = version_helper(ver1_iter, &ver1_iter);
}
if (ver2_iter != NULL) {
digit2 = version_helper(ver2_iter, &ver2_iter);
}
if (digit1 < digit2) {
rc = -1;
break;
} else if (digit1 > digit2) {
rc = 1;
break;
}
if (ver1_iter != NULL && *ver1_iter == '.') {
ver1_iter++;
}
if (ver1_iter != NULL && *ver1_iter == '\0') {
ver1_iter = NULL;
}
if (ver2_iter != NULL && *ver2_iter == '.') {
ver2_iter++;
}
if (ver2_iter != NULL && *ver2_iter == 0) {
ver2_iter = NULL;
}
}
if (rc == 0) {
crm_trace("%s == %s (%d)", version1, version2, lpc);
} else if (rc < 0) {
crm_trace("%s < %s (%d)", version1, version2, lpc);
} else if (rc > 0) {
crm_trace("%s > %s (%d)", version1, version2, lpc);
}
return rc;
}
-/*!
- * \internal
- * \brief Log a failed assertion
- *
- * \param[in] file File making the assertion
- * \param[in] function Function making the assertion
- * \param[in] line Line of file making the assertion
- * \param[in] assert_condition String representation of assertion
- */
-static void
-log_assertion_as(const char *file, const char *function, int line,
- const char *assert_condition)
-{
- if (!pcmk__is_daemon) {
- crm_enable_stderr(TRUE); // Make sure command-line user sees message
- }
- crm_err("%s: Triggered fatal assertion at %s:%d : %s",
- function, file, line, assert_condition);
-}
-
-/* coverity[+kill] */
-/*!
- * \internal
- * \brief Log a failed assertion and abort
- *
- * \param[in] file File making the assertion
- * \param[in] function Function making the assertion
- * \param[in] line Line of file making the assertion
- * \param[in] assert_condition String representation of assertion
- *
- * \note This does not return
- */
-static _Noreturn void
-abort_as(const char *file, const char *function, int line,
- const char *assert_condition)
-{
- log_assertion_as(file, function, line, assert_condition);
- abort();
-}
-
-/* coverity[+kill] */
-/*!
- * \internal
- * \brief Handle a failed assertion
- *
- * When called by a daemon, fork a child that aborts (to dump core), otherwise
- * abort the current process.
- *
- * \param[in] file File making the assertion
- * \param[in] function Function making the assertion
- * \param[in] line Line of file making the assertion
- * \param[in] assert_condition String representation of assertion
- */
-static void
-fail_assert_as(const char *file, const char *function, int line,
- const char *assert_condition)
-{
- int status = 0;
- pid_t pid = 0;
-
- if (!pcmk__is_daemon) {
- abort_as(file, function, line, assert_condition); // does not return
- }
-
- pid = fork();
- switch (pid) {
- case -1: // Fork failed
- crm_warn("%s: Cannot dump core for non-fatal assertion at %s:%d "
- ": %s", function, file, line, assert_condition);
- break;
-
- case 0: // Child process: just abort to dump core
- abort();
- break;
-
- default: // Parent process: wait for child
- crm_err("%s: Forked child [%d] to record non-fatal assertion at "
- "%s:%d : %s", function, pid, file, line, assert_condition);
- crm_write_blackbox(SIGTRAP, NULL);
- do {
- if (waitpid(pid, &status, 0) == pid) {
- return; // Child finished dumping core
- }
- } while (errno == EINTR);
- if (errno == ECHILD) {
- // crm_mon ignores SIGCHLD
- crm_trace("Cannot wait on forked child [%d] "
- "(SIGCHLD is probably ignored)", pid);
- } else {
- crm_err("Cannot wait on forked child [%d]: %s",
- pid, pcmk_rc_str(errno));
- }
- break;
- }
-}
-
-/* coverity[+kill] */
-void
-crm_abort(const char *file, const char *function, int line,
- const char *assert_condition, gboolean do_core, gboolean do_fork)
-{
- if (!do_fork) {
- abort_as(file, function, line, assert_condition);
- } else if (do_core) {
- fail_assert_as(file, function, line, assert_condition);
- } else {
- log_assertion_as(file, function, line, assert_condition);
- }
-}
-
/*!
* \internal
* \brief Convert the current process to a daemon process
*
* Fork a child process, exit the parent, create a PID file with the current
* process ID, and close the standard input/output/error file descriptors.
* Exit instead if a daemon is already running and using the PID file.
*
* \param[in] name Daemon executable name
* \param[in] pidfile File name to use as PID file
*/
void
pcmk__daemonize(const char *name, const char *pidfile)
{
int rc;
pid_t pid;
/* Check before we even try... */
rc = pcmk__pidfile_matches(pidfile, 1, name, &pid);
if ((rc != pcmk_rc_ok) && (rc != ENOENT)) {
crm_err("%s: already running [pid %lld in %s]",
name, (long long) pid, pidfile);
printf("%s: already running [pid %lld in %s]\n",
name, (long long) pid, pidfile);
crm_exit(CRM_EX_ERROR);
}
pid = fork();
if (pid < 0) {
fprintf(stderr, "%s: could not start daemon\n", name);
crm_perror(LOG_ERR, "fork");
crm_exit(CRM_EX_OSERR);
} else if (pid > 0) {
crm_exit(CRM_EX_OK);
}
rc = pcmk__lock_pidfile(pidfile, name);
if (rc != pcmk_rc_ok) {
crm_err("Could not lock '%s' for %s: %s " CRM_XS " rc=%d",
pidfile, name, pcmk_rc_str(rc), rc);
printf("Could not lock '%s' for %s: %s (%d)\n",
pidfile, name, pcmk_rc_str(rc), rc);
crm_exit(CRM_EX_ERROR);
}
umask(S_IWGRP | S_IWOTH | S_IROTH);
close(STDIN_FILENO);
pcmk__open_devnull(O_RDONLY); // stdin (fd 0)
close(STDOUT_FILENO);
pcmk__open_devnull(O_WRONLY); // stdout (fd 1)
close(STDERR_FILENO);
pcmk__open_devnull(O_WRONLY); // stderr (fd 2)
}
#ifdef HAVE_UUID_UUID_H
# include <uuid/uuid.h>
#endif
char *
crm_generate_uuid(void)
{
unsigned char uuid[16];
char *buffer = malloc(37); /* Including NUL byte */
pcmk__mem_assert(buffer);
uuid_generate(uuid);
uuid_unparse(uuid, buffer);
return buffer;
}
#ifdef HAVE_GNUTLS_GNUTLS_H
void
crm_gnutls_global_init(void)
{
signal(SIGPIPE, SIG_IGN);
gnutls_global_init();
}
#endif
bool
pcmk_str_is_infinity(const char *s) {
return pcmk__str_any_of(s, PCMK_VALUE_INFINITY, PCMK_VALUE_PLUS_INFINITY,
NULL);
}
bool
pcmk_str_is_minus_infinity(const char *s) {
return pcmk__str_eq(s, PCMK_VALUE_MINUS_INFINITY, pcmk__str_none);
}
/*!
* \internal
* \brief Sleep for given milliseconds
*
* \param[in] ms Time to sleep
*
* \note The full time might not be slept if a signal is received.
*/
void
pcmk__sleep_ms(unsigned int ms)
{
// @TODO Impose a sane maximum sleep to avoid hanging a process for long
//CRM_CHECK(ms <= MAX_SLEEP, ms = MAX_SLEEP);
// Use sleep() for any whole seconds
if (ms >= 1000) {
sleep(ms / 1000);
ms -= ms / 1000;
}
if (ms == 0) {
return;
}
#if defined(HAVE_NANOSLEEP)
// nanosleep() is POSIX-2008, so prefer that
{
struct timespec req = { .tv_sec = 0, .tv_nsec = (long) (ms * 1000000) };
nanosleep(&req, NULL);
}
#elif defined(HAVE_USLEEP)
// usleep() is widely available, though considered obsolete
usleep((useconds_t) ms);
#else
// Otherwise use a trick with select() timeout
{
struct timeval tv = { .tv_sec = 0, .tv_usec = (suseconds_t) ms };
select(0, NULL, NULL, NULL, &tv);
}
#endif
}
// Deprecated functions kept only for backward API compatibility
// LCOV_EXCL_START
#include <crm/common/util_compat.h>
guint
crm_parse_interval_spec(const char *input)
{
long long msec = -1;
errno = 0;
if (input == NULL) {
return 0;
} else if (input[0] == 'P') {
crm_time_t *period_s = crm_time_parse_duration(input);
if (period_s) {
msec = 1000 * crm_time_get_seconds(period_s);
crm_time_free(period_s);
}
} else {
msec = crm_get_msec(input);
}
if (msec < 0) {
crm_warn("Using 0 instead of '%s'", input);
errno = EINVAL;
return 0;
}
return (msec >= G_MAXUINT)? G_MAXUINT : (guint) msec;
}
char *
pcmk_hostname(void)
{
struct utsname hostinfo;
return (uname(&hostinfo) < 0)? NULL : strdup(hostinfo.nodename);
}
// LCOV_EXCL_STOP
// End deprecated API

File Metadata

Mime Type
text/x-diff
Expires
Thu, Jul 10, 2:16 AM (1 d, 17 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2009716
Default Alt Text
(122 KB)

Event Timeline