diff --git a/include/crm/common/results.h b/include/crm/common/results.h index d64ea57c86..dc1b0f2a69 100644 --- a/include/crm/common/results.h +++ b/include/crm/common/results.h @@ -1,399 +1,405 @@ /* * 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 // 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 }; // 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 f0882731b7..11388b028f 100644 --- a/include/crm/common/util.h +++ b/include/crm/common/util.h @@ -1,103 +1,99 @@ /* * 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 // gid_t, mode_t, size_t, time_t, uid_t #include #include #include // uint32_t #include #include #include #include #include #include #include #include #include #include #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); 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); int crm_user_lookup(const char *name, uid_t * uid, gid_t * gid); int pcmk_daemon_user(uid_t *uid, gid_t *gid); void crm_gnutls_global_init(void); #ifdef __cplusplus } #endif #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) #include #endif #endif diff --git a/lib/common/results.c b/lib/common/results.c index ddd098f238..e5f3e1c012 100644 --- a/lib/common/results.c +++ b/lib/common/results.c @@ -1,1091 +1,1203 @@ /* * 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 #include #include #include #include #include +#include +#include #include #include #include G_DEFINE_QUARK(pcmk-rc-error-quark, pcmk__rc_error) G_DEFINE_QUARK(pcmk-exitc-error-quark, pcmk__exitc_error) // General (all result code types) /*! * \brief Get the name and description of a given result code * * A result code can be interpreted as a member of any one of several families. * * \param[in] code The result code to look up * \param[in] type How \p code should be interpreted * \param[out] name Where to store the result code's name * \param[out] desc Where to store the result code's description * * \return Standard Pacemaker return code */ int pcmk_result_get_strings(int code, enum pcmk_result_type type, const char **name, const char **desc) { const char *code_name = NULL; const char *code_desc = NULL; switch (type) { case pcmk_result_legacy: code_name = pcmk_errorname(code); code_desc = pcmk_strerror(code); break; case pcmk_result_rc: code_name = pcmk_rc_name(code); code_desc = pcmk_rc_str(code); break; case pcmk_result_exitcode: code_name = crm_exit_name(code); code_desc = crm_exit_str((crm_exit_t) code); break; default: return pcmk_rc_undetermined; } if (name != NULL) { *name = code_name; } if (desc != NULL) { *desc = code_desc; } return pcmk_rc_ok; } /*! * \internal * \brief Get the lower and upper bounds of a result code family * * \param[in] type Type of result code * \param[out] lower Where to store the lower bound * \param[out] upper Where to store the upper bound * * \return Standard Pacemaker return code * * \note There is no true upper bound on standard Pacemaker return codes or * legacy return codes. All system \p errno values are valid members of * these result code families, and there is no global upper limit nor a * constant by which to refer to the highest \p errno value on a given * system. */ int pcmk__result_bounds(enum pcmk_result_type type, int *lower, int *upper) { 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; } +/*! + * \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); + } +} + // @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 enum pcmk_rc_e values, excluding \c pcmk_rc_ok * * This constant stores the number of negative standard Pacemaker return codes. * These represent Pacemaker-custom error codes. The count does not include * positive system error numbers, nor does it include \c pcmk_rc_ok (success). */ const size_t pcmk__n_rc = PCMK__NELEM(pcmk__rcs); /*! * \brief Get a return code constant name as a string * * \param[in] rc Integer return code to convert * * \return String of constant name corresponding to rc */ const char * pcmk_rc_name(int rc) { if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < pcmk__n_rc)) { return pcmk__rcs[pcmk_rc_error - rc].name; } switch (rc) { case pcmk_rc_ok: return "pcmk_rc_ok"; case E2BIG: return "E2BIG"; case EACCES: return "EACCES"; case EADDRINUSE: return "EADDRINUSE"; case EADDRNOTAVAIL: return "EADDRNOTAVAIL"; case EAFNOSUPPORT: return "EAFNOSUPPORT"; case EAGAIN: return "EAGAIN"; case EALREADY: return "EALREADY"; case EBADF: return "EBADF"; case EBADMSG: return "EBADMSG"; case EBUSY: return "EBUSY"; case ECANCELED: return "ECANCELED"; case ECHILD: return "ECHILD"; case ECOMM: return "ECOMM"; case ECONNABORTED: return "ECONNABORTED"; case ECONNREFUSED: return "ECONNREFUSED"; case ECONNRESET: return "ECONNRESET"; /* case EDEADLK: return "EDEADLK"; */ case EDESTADDRREQ: return "EDESTADDRREQ"; case EDOM: return "EDOM"; case EDQUOT: return "EDQUOT"; case EEXIST: return "EEXIST"; case EFAULT: return "EFAULT"; case EFBIG: return "EFBIG"; case EHOSTDOWN: return "EHOSTDOWN"; case EHOSTUNREACH: return "EHOSTUNREACH"; case EIDRM: return "EIDRM"; case EILSEQ: return "EILSEQ"; case EINPROGRESS: return "EINPROGRESS"; case EINTR: return "EINTR"; case EINVAL: return "EINVAL"; case EIO: return "EIO"; case EISCONN: return "EISCONN"; case EISDIR: return "EISDIR"; case ELIBACC: return "ELIBACC"; case ELOOP: return "ELOOP"; case EMFILE: return "EMFILE"; case EMLINK: return "EMLINK"; case EMSGSIZE: return "EMSGSIZE"; #ifdef EMULTIHOP // Not available on OpenBSD case EMULTIHOP: return "EMULTIHOP"; #endif case ENAMETOOLONG: return "ENAMETOOLONG"; case ENETDOWN: return "ENETDOWN"; case ENETRESET: return "ENETRESET"; case ENETUNREACH: return "ENETUNREACH"; case ENFILE: return "ENFILE"; case ENOBUFS: return "ENOBUFS"; case ENODATA: return "ENODATA"; case ENODEV: return "ENODEV"; case ENOENT: return "ENOENT"; case ENOEXEC: return "ENOEXEC"; case ENOKEY: return "ENOKEY"; case ENOLCK: return "ENOLCK"; #ifdef ENOLINK // Not available on OpenBSD case ENOLINK: return "ENOLINK"; #endif case ENOMEM: return "ENOMEM"; case ENOMSG: return "ENOMSG"; case ENOPROTOOPT: return "ENOPROTOOPT"; case ENOSPC: return "ENOSPC"; #ifdef ENOSR case ENOSR: return "ENOSR"; #endif #ifdef ENOSTR case ENOSTR: return "ENOSTR"; #endif case ENOSYS: return "ENOSYS"; case ENOTBLK: return "ENOTBLK"; case ENOTCONN: return "ENOTCONN"; case ENOTDIR: return "ENOTDIR"; case ENOTEMPTY: return "ENOTEMPTY"; case ENOTSOCK: return "ENOTSOCK"; #if ENOTSUP != EOPNOTSUPP case ENOTSUP: return "ENOTSUP"; #endif case ENOTTY: return "ENOTTY"; case ENOTUNIQ: return "ENOTUNIQ"; case ENXIO: return "ENXIO"; case EOPNOTSUPP: return "EOPNOTSUPP"; case EOVERFLOW: return "EOVERFLOW"; case EPERM: return "EPERM"; case EPFNOSUPPORT: return "EPFNOSUPPORT"; case EPIPE: return "EPIPE"; case EPROTO: return "EPROTO"; case EPROTONOSUPPORT: return "EPROTONOSUPPORT"; case EPROTOTYPE: return "EPROTOTYPE"; case ERANGE: return "ERANGE"; case EREMOTE: return "EREMOTE"; case EREMOTEIO: return "EREMOTEIO"; case EROFS: return "EROFS"; case ESHUTDOWN: return "ESHUTDOWN"; case ESPIPE: return "ESPIPE"; case ESOCKTNOSUPPORT: return "ESOCKTNOSUPPORT"; case ESRCH: return "ESRCH"; case ESTALE: return "ESTALE"; case ETIME: return "ETIME"; case ETIMEDOUT: return "ETIMEDOUT"; case ETXTBSY: return "ETXTBSY"; #ifdef EUNATCH case EUNATCH: return "EUNATCH"; #endif case EUSERS: return "EUSERS"; /* case EWOULDBLOCK: return "EWOULDBLOCK"; */ case EXDEV: return "EXDEV"; #ifdef EBADE // Not available on OS X case EBADE: return "EBADE"; case EBADFD: return "EBADFD"; case EBADSLT: return "EBADSLT"; case EDEADLOCK: return "EDEADLOCK"; case EBADR: return "EBADR"; case EBADRQC: return "EBADRQC"; case ECHRNG: return "ECHRNG"; #ifdef EISNAM // Not available on OS X, Illumos, Solaris case EISNAM: return "EISNAM"; case EKEYEXPIRED: return "EKEYEXPIRED"; case EKEYREVOKED: return "EKEYREVOKED"; #endif case EKEYREJECTED: return "EKEYREJECTED"; case EL2HLT: return "EL2HLT"; case EL2NSYNC: return "EL2NSYNC"; case EL3HLT: return "EL3HLT"; case EL3RST: return "EL3RST"; case ELIBBAD: return "ELIBBAD"; case ELIBMAX: return "ELIBMAX"; case ELIBSCN: return "ELIBSCN"; case ELIBEXEC: return "ELIBEXEC"; #ifdef ENOMEDIUM // Not available on OS X, Illumos, Solaris case ENOMEDIUM: return "ENOMEDIUM"; case EMEDIUMTYPE: return "EMEDIUMTYPE"; #endif case ENONET: return "ENONET"; case ENOPKG: return "ENOPKG"; case EREMCHG: return "EREMCHG"; case ERESTART: return "ERESTART"; case ESTRPIPE: return "ESTRPIPE"; #ifdef EUCLEAN // Not available on OS X, Illumos, Solaris case EUCLEAN: return "EUCLEAN"; #endif case EXFULL: return "EXFULL"; #endif // EBADE default: return "Unknown"; } } /*! * \brief Get a user-friendly description of a return code * * \param[in] rc Integer return code to convert * * \return String description of rc */ const char * pcmk_rc_str(int rc) { if (rc == pcmk_rc_ok) { return "OK"; } if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < pcmk__n_rc)) { return pcmk__rcs[pcmk_rc_error - rc].desc; } if (rc < 0) { return "Error"; } // Handle values that could be defined by system or by portability.h switch (rc) { #ifdef PCMK__ENOTUNIQ case ENOTUNIQ: return "Name not unique on network"; #endif #ifdef PCMK__ECOMM case ECOMM: return "Communication error on send"; #endif #ifdef PCMK__ELIBACC case ELIBACC: return "Can not access a needed shared library"; #endif #ifdef PCMK__EREMOTEIO case EREMOTEIO: return "Remote I/O error"; #endif #ifdef PCMK__ENOKEY case ENOKEY: return "Required key not available"; #endif #ifdef PCMK__ENODATA case ENODATA: return "No data available"; #endif #ifdef PCMK__ETIME case ETIME: return "Timer expired"; #endif #ifdef PCMK__EKEYREJECTED case EKEYREJECTED: return "Key was rejected by service"; #endif default: return strerror(rc); } } // This returns negative values for errors //! \deprecated Use standard return codes instead int pcmk_rc2legacy(int rc) { if (rc >= 0) { return -rc; // OK or system errno } if ((rc <= pcmk_rc_error) && ((pcmk_rc_error - rc) < pcmk__n_rc)) { return pcmk__rcs[pcmk_rc_error - rc].legacy_rc; } return -pcmk_err_generic; } //! \deprecated Use standard return codes instead int pcmk_legacy2rc(int legacy_rc) { legacy_rc = abs(legacy_rc); switch (legacy_rc) { case pcmk_err_no_quorum: return pcmk_rc_no_quorum; case pcmk_err_schema_validation: return pcmk_rc_schema_validation; case pcmk_err_schema_unchanged: return pcmk_rc_schema_unchanged; case pcmk_err_transform_failed: return pcmk_rc_transform_failed; case pcmk_err_old_data: return pcmk_rc_old_data; case pcmk_err_diff_failed: return pcmk_rc_diff_failed; case pcmk_err_diff_resync: return pcmk_rc_diff_resync; case pcmk_err_cib_modified: return pcmk_rc_cib_modified; case pcmk_err_cib_backup: return pcmk_rc_cib_backup; case pcmk_err_cib_save: return pcmk_rc_cib_save; case pcmk_err_cib_corrupt: return pcmk_rc_cib_corrupt; case pcmk_err_multiple: return pcmk_rc_multiple; case pcmk_err_node_unknown: return pcmk_rc_node_unknown; case pcmk_err_already: return pcmk_rc_already; case pcmk_err_bad_nvpair: return pcmk_rc_bad_nvpair; case pcmk_err_unknown_format: return pcmk_rc_unknown_format; case pcmk_err_generic: return pcmk_rc_error; case pcmk_ok: return pcmk_rc_ok; default: return legacy_rc; // system errno } } // Exit status codes const char * crm_exit_name(crm_exit_t exit_code) { switch (exit_code) { case CRM_EX_OK: return "CRM_EX_OK"; case CRM_EX_ERROR: return "CRM_EX_ERROR"; case CRM_EX_INVALID_PARAM: return "CRM_EX_INVALID_PARAM"; case CRM_EX_UNIMPLEMENT_FEATURE: return "CRM_EX_UNIMPLEMENT_FEATURE"; case CRM_EX_INSUFFICIENT_PRIV: return "CRM_EX_INSUFFICIENT_PRIV"; case CRM_EX_NOT_INSTALLED: return "CRM_EX_NOT_INSTALLED"; case CRM_EX_NOT_CONFIGURED: return "CRM_EX_NOT_CONFIGURED"; case CRM_EX_NOT_RUNNING: return "CRM_EX_NOT_RUNNING"; case CRM_EX_PROMOTED: return "CRM_EX_PROMOTED"; case CRM_EX_FAILED_PROMOTED: return "CRM_EX_FAILED_PROMOTED"; case CRM_EX_USAGE: return "CRM_EX_USAGE"; case CRM_EX_DATAERR: return "CRM_EX_DATAERR"; case CRM_EX_NOINPUT: return "CRM_EX_NOINPUT"; case CRM_EX_NOUSER: return "CRM_EX_NOUSER"; case CRM_EX_NOHOST: return "CRM_EX_NOHOST"; case CRM_EX_UNAVAILABLE: return "CRM_EX_UNAVAILABLE"; case CRM_EX_SOFTWARE: return "CRM_EX_SOFTWARE"; case CRM_EX_OSERR: return "CRM_EX_OSERR"; case CRM_EX_OSFILE: return "CRM_EX_OSFILE"; case CRM_EX_CANTCREAT: return "CRM_EX_CANTCREAT"; case CRM_EX_IOERR: return "CRM_EX_IOERR"; case CRM_EX_TEMPFAIL: return "CRM_EX_TEMPFAIL"; case CRM_EX_PROTOCOL: return "CRM_EX_PROTOCOL"; case CRM_EX_NOPERM: return "CRM_EX_NOPERM"; case CRM_EX_CONFIG: return "CRM_EX_CONFIG"; case CRM_EX_FATAL: return "CRM_EX_FATAL"; case CRM_EX_PANIC: return "CRM_EX_PANIC"; case CRM_EX_DISCONNECT: return "CRM_EX_DISCONNECT"; case CRM_EX_DIGEST: return "CRM_EX_DIGEST"; case CRM_EX_NOSUCH: return "CRM_EX_NOSUCH"; case CRM_EX_QUORUM: return "CRM_EX_QUORUM"; case CRM_EX_UNSAFE: return "CRM_EX_UNSAFE"; case CRM_EX_EXISTS: return "CRM_EX_EXISTS"; case CRM_EX_MULTIPLE: return "CRM_EX_MULTIPLE"; case CRM_EX_EXPIRED: return "CRM_EX_EXPIRED"; case CRM_EX_NOT_YET_IN_EFFECT: return "CRM_EX_NOT_YET_IN_EFFECT"; case CRM_EX_INDETERMINATE: return "CRM_EX_INDETERMINATE"; case CRM_EX_UNSATISFIED: return "CRM_EX_UNSATISFIED"; case CRM_EX_OLD: return "CRM_EX_OLD"; case CRM_EX_TIMEOUT: return "CRM_EX_TIMEOUT"; case CRM_EX_DEGRADED: return "CRM_EX_DEGRADED"; case CRM_EX_DEGRADED_PROMOTED: return "CRM_EX_DEGRADED_PROMOTED"; case CRM_EX_NONE: return "CRM_EX_NONE"; case CRM_EX_MAX: return "CRM_EX_UNKNOWN"; } return "CRM_EX_UNKNOWN"; } const char * crm_exit_str(crm_exit_t exit_code) { switch (exit_code) { case CRM_EX_OK: return "OK"; case CRM_EX_ERROR: return "Error occurred"; case CRM_EX_INVALID_PARAM: return "Invalid parameter"; case CRM_EX_UNIMPLEMENT_FEATURE: return "Unimplemented"; case CRM_EX_INSUFFICIENT_PRIV: return "Insufficient privileges"; case CRM_EX_NOT_INSTALLED: return "Not installed"; case CRM_EX_NOT_CONFIGURED: return "Not configured"; case CRM_EX_NOT_RUNNING: return "Not running"; case CRM_EX_PROMOTED: return "Promoted"; case CRM_EX_FAILED_PROMOTED: return "Failed in promoted role"; case CRM_EX_USAGE: return "Incorrect usage"; case CRM_EX_DATAERR: return "Invalid data given"; case CRM_EX_NOINPUT: return "Input file not available"; case CRM_EX_NOUSER: return "User does not exist"; case CRM_EX_NOHOST: return "Host does not exist"; case CRM_EX_UNAVAILABLE: return "Necessary service unavailable"; case CRM_EX_SOFTWARE: return "Internal software bug"; case CRM_EX_OSERR: return "Operating system error occurred"; case CRM_EX_OSFILE: return "System file not available"; case CRM_EX_CANTCREAT: return "Cannot create output file"; case CRM_EX_IOERR: return "I/O error occurred"; case CRM_EX_TEMPFAIL: return "Temporary failure, try again"; case CRM_EX_PROTOCOL: return "Protocol violated"; case CRM_EX_NOPERM: return "Insufficient privileges"; case CRM_EX_CONFIG: return "Invalid configuration"; case CRM_EX_FATAL: return "Fatal error occurred, will not respawn"; case CRM_EX_PANIC: return "System panic required"; case CRM_EX_DISCONNECT: return "Not connected"; case CRM_EX_DIGEST: return "Digest mismatch"; case CRM_EX_NOSUCH: return "No such object"; case CRM_EX_QUORUM: return "Quorum required"; case CRM_EX_UNSAFE: return "Operation not safe"; case CRM_EX_EXISTS: return "Requested item already exists"; case CRM_EX_MULTIPLE: return "Multiple items match request"; case CRM_EX_EXPIRED: return "Requested item has expired"; case CRM_EX_NOT_YET_IN_EFFECT: return "Requested item is not yet in effect"; case CRM_EX_INDETERMINATE: return "Could not determine status"; case CRM_EX_UNSATISFIED: return "Not applicable under current conditions"; case CRM_EX_OLD: return "Update was older than existing configuration"; case CRM_EX_TIMEOUT: return "Timeout occurred"; case CRM_EX_DEGRADED: return "Service is active but might fail soon"; case CRM_EX_DEGRADED_PROMOTED: return "Service is promoted but might fail soon"; case CRM_EX_NONE: return "No exit status available"; case CRM_EX_MAX: return "Error occurred"; } if ((exit_code > 128) && (exit_code < CRM_EX_MAX)) { return "Interrupted by signal"; } return "Unknown exit status"; } /*! * \brief Map a function return code to the most similar exit code * * \param[in] rc Function return code * * \return Most similar exit code */ crm_exit_t pcmk_rc2exitc(int rc) { switch (rc) { case pcmk_rc_ok: case pcmk_rc_no_output: // quiet mode, or nothing to output return CRM_EX_OK; case pcmk_rc_no_quorum: return CRM_EX_QUORUM; case pcmk_rc_old_data: return CRM_EX_OLD; case pcmk_rc_schema_validation: case pcmk_rc_transform_failed: case pcmk_rc_unpack_error: return CRM_EX_CONFIG; case pcmk_rc_bad_nvpair: return CRM_EX_INVALID_PARAM; case EACCES: return CRM_EX_INSUFFICIENT_PRIV; case EBADF: case EINVAL: case EFAULT: case ENOSYS: case EOVERFLOW: case pcmk_rc_underflow: 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(); pcmk__xml_cleanup(); if (crm_system_name) { crm_info("Exiting %s " QB_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); } diff --git a/lib/common/strings.c b/lib/common/strings.c index 3c719e95f5..efdba032b6 100644 --- a/lib/common/strings.c +++ b/lib/common/strings.c @@ -1,1314 +1,1313 @@ /* * 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 #include #include #include #include #include #include // DBL_MIN #include #include #include /*! * \internal * \brief Scan a long long integer from a string * * \param[in] text String to scan * \param[out] result If not NULL, where to store scanned value * \param[in] default_value Value to use if text is NULL or invalid * \param[out] end_text If not NULL, where to store pointer to first * non-integer character * * \return Standard Pacemaker return code (\c pcmk_rc_ok on success, * \c pcmk_rc_bad_input on failed string conversion due to invalid * input, or \c ERANGE if outside long long range) * \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 = errno; crm_debug("Integer parsed from '%s' was clipped to %lld", text, local_result); } else if (local_end_text == text) { rc = pcmk_rc_bad_input; 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 = scan_ll(text, &local_result, default_value, NULL); 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 == ERANGE) && (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, PCMK_VALUE_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 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 " QB_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')) { rc = pcmk_rc_bad_input; } return rc; } 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-NULL string. * * The \p pcmk__str_none flag produces the default behavior. Behavior can be * changed with various flags: * * - \p pcmk__str_regex - The second string is a regular expression that the * first string will be matched against. * - \p pcmk__str_casei - By default, comparisons are done taking case into * account. This flag makes comparisons case- * insensitive. This can be combined with * \p pcmk__str_regex. * - \p pcmk__str_null_matches - If one string is \p NULL and the other is not, * still return \p 0. * - \p pcmk__str_star_matches - If one string is \p "*" and the other is not, * still return \p 0. * * \param[in] s1 First string to compare * \param[in] s2 Second string to compare, or a regular expression to * match if \p pcmk__str_regex is set * \param[in] flags A bitfield of \p pcmk__str_flags to modify operation * * \retval negative \p s1 is \p NULL or comes before \p s2 * \retval 0 \p s1 and \p s2 are equal, or \p s1 is found in \p s2 if * \c pcmk__str_regex is set * \retval positive \p s2 is \p NULL or \p s1 comes after \p s2, or \p s2 * is an invalid regular expression, or \p s1 was not found * in \p s2 if \p pcmk__str_regex is set. */ int pcmk__strcmp(const char *s1, const char *s2, uint32_t flags) { /* If this flag is set, the second string is a regex. */ if (pcmk_is_set(flags, pcmk__str_regex)) { regex_t r_patt; int reg_flags = REG_EXTENDED | REG_NOSUB; int regcomp_rc = 0; int rc = 0; if (s1 == NULL || s2 == NULL) { return 1; } if (pcmk_is_set(flags, pcmk__str_casei)) { reg_flags |= REG_ICASE; } regcomp_rc = regcomp(&r_patt, s2, reg_flags); if (regcomp_rc != 0) { rc = 1; crm_err("Bad regex '%s' for update: %s", s2, strerror(regcomp_rc)); } else { rc = regexec(&r_patt, s1, 0, NULL, 0); regfree(&r_patt); if (rc != 0) { rc = 1; } } return rc; } /* If the strings are the same pointer, return 0 immediately. */ if (s1 == s2) { return 0; } /* If this flag is set, return 0 if either (or both) of the input strings * are NULL. If neither one is NULL, we need to continue and compare * them normally. */ if (pcmk_is_set(flags, pcmk__str_null_matches)) { if (s1 == NULL || s2 == NULL) { return 0; } } /* Handle the cases where one is NULL and the str_null_matches flag is not set. * A NULL string always sorts to the beginning. */ if (s1 == NULL) { return -1; } else if (s2 == NULL) { return 1; } /* If this flag is set, return 0 if either (or both) of the input strings * are "*". If neither one is, we need to continue and compare them * normally. */ if (pcmk_is_set(flags, pcmk__str_star_matches)) { if (strcmp(s1, "*") == 0 || strcmp(s2, "*") == 0) { return 0; } } if (pcmk_is_set(flags, pcmk__str_casei)) { return strcasecmp(s1, s2); } else { return strcmp(s1, s2); } } /*! * \internal * \brief 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 NULL-terminated list of strings * * \note This tends to be more efficient than a single call to * \p g_string_append_printf(). */ void pcmk__g_strcat(GString *buffer, ...) { va_list ap; CRM_ASSERT(buffer != NULL); va_start(ap, buffer); while (true) { const char *ele = va_arg(ap, const char *); if (ele == NULL) { break; } g_string_append(buffer, ele); } va_end(ap); } 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 fe8b4f042e..35c21672b8 100644 --- a/lib/common/tests/strings/pcmk__parse_ll_range_test.c +++ b/lib/common/tests/strings/pcmk__parse_ll_range_test.c @@ -1,121 +1,120 @@ /* * 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 #include 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), ERANGE); assert_int_equal(pcmk__parse_ll_range("100-20000000000000000000", &start, &end), ERANGE); } 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 7968a0f5ad..55c7d3bf34 100644 --- a/lib/common/utils.c +++ b/lib/common/utils.c @@ -1,529 +1,417 @@ /* * 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 -#include -#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "crmcommon_private.h" CRM_TRACE_INIT_DATA(common); bool pcmk__config_has_error = false; bool pcmk__config_has_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 " QB_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 #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; } void crm_gnutls_global_init(void) { signal(SIGPIPE, SIG_IGN); gnutls_global_init(); } /*! * \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 /*! * \brief Check whether string represents a client name used by cluster daemons * * \param[in] name String to check * * \return true if name is standard client name used by daemons, false otherwise * * \note This is provided by the client, and so cannot be used by itself as a * secure means of authentication. */ bool crm_is_daemon_name(const char *name) { return pcmk__str_any_of(name, "attrd", CRM_SYSTEM_CIB, CRM_SYSTEM_CRMD, CRM_SYSTEM_DC, CRM_SYSTEM_LRMD, CRM_SYSTEM_MCP, CRM_SYSTEM_PENGINE, CRM_SYSTEM_TENGINE, "pacemaker-attrd", "pacemaker-based", "pacemaker-controld", "pacemaker-execd", "pacemaker-fenced", "pacemaker-remoted", "pacemaker-schedulerd", "stonith-ng", "stonithd", NULL); } // LCOV_EXCL_STOP // End deprecated API