diff --git a/include/crm/Makefile.am b/include/crm/Makefile.am index 7e11b5e3f9..d1e2452088 100644 --- a/include/crm/Makefile.am +++ b/include/crm/Makefile.am @@ -1,20 +1,21 @@ # # Copyright 2004-2021 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. # MAINTAINERCLEANFILES = Makefile.in headerdir=$(pkgincludedir)/crm header_HEADERS = cib.h cluster.h compatibility.h crm.h \ lrmd.h msg_xml.h services.h stonith-ng.h \ + crm_compat.h \ msg_xml_compat.h noinst_HEADERS = lrmd_internal.h SUBDIRS = common pengine cib fencing cluster diff --git a/include/crm/crm.h b/include/crm/crm.h index 0d01421517..c140ebc7bb 100644 --- a/include/crm/crm.h +++ b/include/crm/crm.h @@ -1,232 +1,235 @@ /* * Copyright 2004-2021 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 CRM__H # define CRM__H #ifdef __cplusplus extern "C" { #endif /** * \file * \brief A dumping ground * \ingroup core */ # include # include # include # include # include # include /*! * The CRM feature set assists with compatibility in mixed-version clusters. * The major version number increases when nodes with different versions * would not work (rolling upgrades are not allowed). The minor version * number increases when mixed-version clusters are allowed only during * rolling upgrades (a node with the oldest feature set will be elected DC). The * minor-minor version number is ignored, but allows resource agents to detect * cluster support for various features. * * The feature set also affects the processing of old saved CIBs (such as for * many scheduler regression tests). * * Particular feature points currently used by pacemaker: * * >2.1: Operation updates include timing data * >=3.0.5: XML v2 digests are created * >=3.0.8: Peers do not need acks for cancellations * >=3.0.9: DC will send its own shutdown request to all peers * XML v2 patchsets are created by default * >=3.0.13: Fail counts include operation name and interval * >=3.2.0: DC supports PCMK_LRM_OP_INVALID and PCMK_LRM_OP_NOT_CONNECTED */ # define CRM_FEATURE_SET "3.7.3" -# define EOS '\0' # define DIMOF(a) ((int) (sizeof(a)/sizeof(a[0])) ) # ifndef MAX_NAME # define MAX_NAME 256 # endif # ifndef __GNUC__ # define __builtin_expect(expr, result) (expr) # endif /* Some handy macros used by the Linux kernel */ # define __likely(expr) __builtin_expect(expr, 1) # define __unlikely(expr) __builtin_expect(expr, 0) # define CRM_META "CRM_meta" extern char *crm_system_name; /* *INDENT-OFF* */ // Used for some internal IPC timeouts (maybe should be configurable option) # define MAX_IPC_DELAY 120 // How we represent "infinite" scores # define CRM_SCORE_INFINITY 1000000 # define CRM_INFINITY_S "INFINITY" # define CRM_PLUS_INFINITY_S "+" CRM_INFINITY_S # define CRM_MINUS_INFINITY_S "-" CRM_INFINITY_S /* @COMPAT API < 2.0.0 Deprecated "infinity" aliases * * INFINITY might be defined elsewhere (e.g. math.h), so undefine it first. * This, of course, complicates any attempt to use the other definition in any * code that includes this header. */ # undef INFINITY # define INFINITY_S "INFINITY" # define MINUS_INFINITY_S "-INFINITY" # define INFINITY 1000000 /* Sub-systems */ # define CRM_SYSTEM_DC "dc" # define CRM_SYSTEM_DCIB "dcib" /* The master CIB */ # define CRM_SYSTEM_CIB "cib" # define CRM_SYSTEM_CRMD "crmd" # define CRM_SYSTEM_LRMD "lrmd" # define CRM_SYSTEM_PENGINE "pengine" # define CRM_SYSTEM_TENGINE "tengine" # define CRM_SYSTEM_STONITHD "stonithd" # define CRM_SYSTEM_MCP "pacemakerd" // Names of internally generated node attributes # define CRM_ATTR_UNAME "#uname" # define CRM_ATTR_ID "#id" # define CRM_ATTR_KIND "#kind" # define CRM_ATTR_ROLE "#role" # define CRM_ATTR_IS_DC "#is_dc" # define CRM_ATTR_CLUSTER_NAME "#cluster-name" # define CRM_ATTR_SITE_NAME "#site-name" # define CRM_ATTR_UNFENCED "#node-unfenced" # define CRM_ATTR_DIGESTS_ALL "#digests-all" # define CRM_ATTR_DIGESTS_SECURE "#digests-secure" # define CRM_ATTR_RA_VERSION "#ra-version" # define CRM_ATTR_PROTOCOL "#attrd-protocol" /* Valid operations */ # define CRM_OP_NOOP "noop" # define CRM_OP_JOIN_ANNOUNCE "join_announce" # define CRM_OP_JOIN_OFFER "join_offer" # define CRM_OP_JOIN_REQUEST "join_request" # define CRM_OP_JOIN_ACKNAK "join_ack_nack" # define CRM_OP_JOIN_CONFIRM "join_confirm" # define CRM_OP_PING "ping" # define CRM_OP_NODE_INFO "node-info" # define CRM_OP_THROTTLE "throttle" # define CRM_OP_VOTE "vote" # define CRM_OP_NOVOTE "no-vote" # define CRM_OP_HELLO "hello" # define CRM_OP_PECALC "pe_calc" # define CRM_OP_QUIT "quit" # define CRM_OP_LOCAL_SHUTDOWN "start_shutdown" # define CRM_OP_SHUTDOWN_REQ "req_shutdown" # define CRM_OP_SHUTDOWN "do_shutdown" # define CRM_OP_FENCE "stonith" # define CRM_OP_REGISTER "register" # define CRM_OP_IPC_FWD "ipc_fwd" # define CRM_OP_INVOKE_LRM "lrm_invoke" # define CRM_OP_LRM_REFRESH "lrm_refresh" /* Deprecated */ # define CRM_OP_LRM_QUERY "lrm_query" # define CRM_OP_LRM_DELETE "lrm_delete" # define CRM_OP_LRM_FAIL "lrm_fail" # define CRM_OP_PROBED "probe_complete" # define CRM_OP_REPROBE "probe_again" # define CRM_OP_CLEAR_FAILCOUNT "clear_failcount" # define CRM_OP_REMOTE_STATE "remote_state" # define CRM_OP_RELAXED_SET "one-or-more" # define CRM_OP_RELAXED_CLONE "clone-one-or-more" # define CRM_OP_RM_NODE_CACHE "rm_node_cache" # define CRM_OP_MAINTENANCE_NODES "maintenance_nodes" /* Possible cluster membership states */ # define CRMD_JOINSTATE_DOWN "down" # define CRMD_JOINSTATE_PENDING "pending" # define CRMD_JOINSTATE_MEMBER "member" # define CRMD_JOINSTATE_NACK "banned" # define CRMD_ACTION_DELETE "delete" # define CRMD_ACTION_CANCEL "cancel" # define CRMD_ACTION_RELOAD "reload" # define CRMD_ACTION_MIGRATE "migrate_to" # define CRMD_ACTION_MIGRATED "migrate_from" # define CRMD_ACTION_START "start" # define CRMD_ACTION_STARTED "running" # define CRMD_ACTION_STOP "stop" # define CRMD_ACTION_STOPPED "stopped" # define CRMD_ACTION_PROMOTE "promote" # define CRMD_ACTION_PROMOTED "promoted" # define CRMD_ACTION_DEMOTE "demote" # define CRMD_ACTION_DEMOTED "demoted" # define CRMD_ACTION_NOTIFY "notify" # define CRMD_ACTION_NOTIFIED "notified" # define CRMD_ACTION_STATUS "monitor" # define CRMD_ACTION_METADATA "meta-data" # define CRMD_METADATA_CALL_TIMEOUT 30000 /* short names */ # define RSC_DELETE CRMD_ACTION_DELETE # define RSC_CANCEL CRMD_ACTION_CANCEL # define RSC_MIGRATE CRMD_ACTION_MIGRATE # define RSC_MIGRATED CRMD_ACTION_MIGRATED # define RSC_START CRMD_ACTION_START # define RSC_STARTED CRMD_ACTION_STARTED # define RSC_STOP CRMD_ACTION_STOP # define RSC_STOPPED CRMD_ACTION_STOPPED # define RSC_PROMOTE CRMD_ACTION_PROMOTE # define RSC_PROMOTED CRMD_ACTION_PROMOTED # define RSC_DEMOTE CRMD_ACTION_DEMOTE # define RSC_DEMOTED CRMD_ACTION_DEMOTED # define RSC_NOTIFY CRMD_ACTION_NOTIFY # define RSC_NOTIFIED CRMD_ACTION_NOTIFIED # define RSC_STATUS CRMD_ACTION_STATUS # define RSC_METADATA CRMD_ACTION_METADATA /* *INDENT-ON* */ typedef GList *GListPtr; # include # include static inline const char * crm_action_str(const char *task, guint interval_ms) { if ((task != NULL) && (interval_ms == 0) && (strcasecmp(task, RSC_STATUS) == 0)) { return "probe"; } return task; } +#ifndef PCMK__NO_COMPAT +#include +#endif + #ifdef __cplusplus } #endif #endif diff --git a/include/crm/crm_compat.h b/include/crm/crm_compat.h new file mode 100644 index 0000000000..f920216b02 --- /dev/null +++ b/include/crm/crm_compat.h @@ -0,0 +1,33 @@ +/* + * Copyright 2004-2021 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_COMPAT__H +# define PCMK__CRM_COMPAT__H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \file + * \brief Deprecated Pacemaker utilities + * \ingroup core + * \deprecated Do not include this header directly. The utilities in this + * header, and the header itself, will be removed in a future + * release. + */ + +//! \deprecated Use '\0' instead +#define EOS '\0' + +#ifdef __cplusplus +} +#endif + +#endif // PCMK__CRM_COMPAT__H diff --git a/include/portability.h b/include/portability.h index 390cacd542..d372cc91e9 100644 --- a/include/portability.h +++ b/include/portability.h @@ -1,162 +1,161 @@ /* * Copyright 2001-2021 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 PORTABILITY_H # define PORTABILITY_H -# define EOS '\0' # define DIMOF(a) ((int) (sizeof(a)/sizeof(a[0])) ) /* Needs to be defined before any other includes, otherwise some system * headers do not behave as expected! Major black magic... */ # undef _GNU_SOURCE /* in case it was defined on the command line */ # define _GNU_SOURCE /* Please leave this as the first #include - Solaris needs it there */ # ifdef HAVE_CONFIG_H # ifndef PCMK__CONFIG_H # define PCMK__CONFIG_H # include # endif # endif /* Prototypes for libreplace functions */ # ifndef HAVE_DAEMON /* We supply a replacement function, but need a prototype */ int daemon(int nochdir, int noclose); # endif /* HAVE_DAEMON */ # ifndef HAVE_SETENV /* We supply a replacement function, but need a prototype */ int setenv(const char *name, const char *value, int why); # endif /* HAVE_SETENV */ # ifndef HAVE_STRERROR /* We supply a replacement function, but need a prototype */ char *strerror(int errnum); # endif /* HAVE_STRERROR */ # ifndef HAVE_STRCHRNUL /* We supply a replacement function, but need a prototype */ char *strchrnul(const char *s, int c_in); # endif /* HAVE_STRCHRNUL */ # ifndef HAVE_ALPHASORT # include int alphasort(const void *dirent1, const void *dirent2); # endif /* HAVE_ALPHASORT */ # ifndef HAVE_STRNLEN size_t strnlen(const char *s, size_t maxlen); # else # define USE_GNU # endif # ifndef HAVE_STRNDUP char *strndup(const char *str, size_t len); # else # define USE_GNU # endif # include # if SUPPORT_DBUS # ifndef HAVE_DBUSBASICVALUE # include # include /** * An 8-byte struct you could use to access int64 without having * int64 support */ typedef struct { uint32_t first32; /**< first 32 bits in the 8 bytes (beware endian issues) */ uint32_t second32; /**< second 32 bits in the 8 bytes (beware endian issues) */ } DBus8ByteStruct; /** * A simple value union that lets you access bytes as if they * were various types; useful when dealing with basic types via * void pointers and varargs. * * This union also contains a pointer member (which can be used * to retrieve a string from dbus_message_iter_get_basic(), for * instance), so on future platforms it could conceivably be larger * than 8 bytes. */ typedef union { unsigned char bytes[8]; /**< as 8 individual bytes */ int16_t i16; /**< as int16 */ uint16_t u16; /**< as int16 */ int32_t i32; /**< as int32 */ uint32_t u32; /**< as int32 */ uint32_t bool_val; /**< as boolean */ # ifdef DBUS_HAVE_INT64 int64_t i64; /**< as int64 */ uint64_t u64; /**< as int64 */ # endif DBus8ByteStruct eight; /**< as 8-byte struct */ double dbl; /**< as double */ unsigned char byt; /**< as byte */ char *str; /**< as char* (string, object path or signature) */ int fd; /**< as Unix file descriptor */ } DBusBasicValue; # endif # endif /* Replacement error codes for non-linux */ # include # ifndef ENOTUNIQ # define ENOTUNIQ 190 # endif # ifndef ECOMM # define ECOMM 191 # endif # ifndef ELIBACC # define ELIBACC 192 # endif # ifndef EREMOTEIO # define EREMOTEIO 193 # endif # ifndef EUNATCH # define EUNATCH 194 # endif # ifndef ENOKEY # define ENOKEY 195 # endif # ifndef ENODATA # define ENODATA 196 # endif # ifndef ETIME # define ETIME 197 # endif # ifndef ENOSR # define ENOSR 198 # endif # ifndef ENOSTR # define ENOSTR 199 # endif # ifndef EKEYREJECTED # define EKEYREJECTED 200 # endif #endif /* PORTABILITY_H */ diff --git a/lib/common/strings.c b/lib/common/strings.c index 48832056f4..421353ea97 100644 --- a/lib/common/strings.c +++ b/lib/common/strings.c @@ -1,1109 +1,1109 @@ /* * Copyright 2004-2021 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include // DBL_MIN #include #include // fabs() #include #include char * crm_itoa_stack(int an_int, char *buffer, size_t len) { if (buffer != NULL) { snprintf(buffer, len, "%d", an_int); } return buffer; } /*! * \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[out] end_text If not NULL, where to store pointer to just after value * * \return Standard Pacemaker return code (\c pcmk_rc_ok on success, * \c EINVAL on failed string conversion due to invalid input, * or \c EOVERFLOW on arithmetic overflow) * \note Sets \c errno on error */ static int scan_ll(const char *text, long long *result, char **end_text) { long long local_result = PCMK__PARSE_INT_DEFAULT; char *local_end_text = NULL; int rc = pcmk_rc_ok; errno = 0; if (text != NULL) { local_result = strtoll(text, &local_end_text, 10); if (errno == ERANGE) { rc = EOVERFLOW; crm_warn("Integer parsed from %s was clipped to %lld", text, local_result); } else if (errno != 0) { rc = errno; local_result = PCMK__PARSE_INT_DEFAULT; crm_warn("Could not parse integer from %s (using %d instead): %s", text, PCMK__PARSE_INT_DEFAULT, pcmk_rc_str(rc)); } else if (local_end_text == text) { rc = EINVAL; local_result = PCMK__PARSE_INT_DEFAULT; crm_warn("Could not parse integer from %s (using %d instead): " "No digits found", text, PCMK__PARSE_INT_DEFAULT); } if ((end_text == NULL) && !pcmk__str_empty(local_end_text)) { crm_warn("Characters left over after parsing '%s': '%s'", text, local_end_text); } errno = rc; } if (end_text != NULL) { *end_text = local_end_text; } if (result != NULL) { *result = local_result; } return rc; } /*! * \brief Parse a long long integer value from a string * * \param[in] text The string to parse * \param[in] default_text Default string to parse if text is NULL * * \return Parsed value on success, PCMK__PARSE_INT_DEFAULT (and set * errno) on error */ long long crm_parse_ll(const char *text, const char *default_text) { long long result; if (text == NULL) { text = default_text; if (text == NULL) { crm_err("No default conversion value supplied"); errno = EINVAL; return PCMK__PARSE_INT_DEFAULT; } } scan_ll(text, &result, NULL); return result; } /*! * \brief Parse an integer value from a string * * \param[in] text The string to parse * \param[in] default_text Default string to parse if text is NULL * * \return Parsed value on success, INT_MIN or INT_MAX (and set errno to * ERANGE) if parsed value is out of integer range, otherwise * PCMK__PARSE_INT_DEFAULT (and set errno) */ int crm_parse_int(const char *text, const char *default_text) { long long result = crm_parse_ll(text, default_text); if (result < INT_MIN) { // If errno is ERANGE, crm_parse_ll() has already logged a message if (errno != ERANGE) { crm_err("Conversion of %s was clipped: %lld", text, result); errno = ERANGE; } return INT_MIN; } else if (result > INT_MAX) { // If errno is ERANGE, crm_parse_ll() has already logged a message if (errno != ERANGE) { crm_err("Conversion of %s was clipped: %lld", text, result); errno = ERANGE; } return INT_MAX; } return (int) result; } /*! * \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 (fabs(*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 (fabs(*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; CRM_CHECK((table != NULL) && (key != NULL), return EINVAL); value = g_hash_table_lookup(table, key); if (value == NULL) { if (result != NULL) { *result = default_val; } return pcmk_rc_ok; } errno = 0; value_ll = crm_parse_ll(value, NULL); if (errno != 0) { return errno; // Message already logged } if ((value_ll < 0) || (value_ll > G_MAXUINT)) { crm_warn("Could not parse non-negative integer from %s", value); return ERANGE; } if (result != NULL) { *result = (guint) value_ll; } return pcmk_rc_ok; } #ifndef NUMCHARS # define NUMCHARS "0123456789." #endif #ifndef WHITESPACE # define WHITESPACE " \t\n\r\f" #endif /*! * \brief Parse a time+units string and return milliseconds equivalent * * \param[in] input String with a number and units (optionally with whitespace * before and/or after the number) * * \return Milliseconds corresponding to string expression, or * PCMK__PARSE_INT_DEFAULT on error */ long long crm_get_msec(const char *input) { const char *num_start = NULL; const char *units; long long multiplier = 1000; long long divisor = 1; long long msec = PCMK__PARSE_INT_DEFAULT; size_t num_len = 0; char *end_text = NULL; if (input == NULL) { return PCMK__PARSE_INT_DEFAULT; } num_start = input + strspn(input, WHITESPACE); num_len = strspn(num_start, NUMCHARS); if (num_len < 1) { return PCMK__PARSE_INT_DEFAULT; } units = num_start + num_len; units += strspn(units, WHITESPACE); if (!strncasecmp(units, "ms", 2) || !strncasecmp(units, "msec", 4)) { multiplier = 1; divisor = 1; } else if (!strncasecmp(units, "us", 2) || !strncasecmp(units, "usec", 4)) { multiplier = 1; divisor = 1000; } else if (!strncasecmp(units, "s", 1) || !strncasecmp(units, "sec", 3)) { multiplier = 1000; divisor = 1; } else if (!strncasecmp(units, "m", 1) || !strncasecmp(units, "min", 3)) { multiplier = 60 * 1000; divisor = 1; } else if (!strncasecmp(units, "h", 1) || !strncasecmp(units, "hr", 2)) { multiplier = 60 * 60 * 1000; divisor = 1; - } else if ((*units != EOS) && (*units != '\n') && (*units != '\r')) { + } else if ((*units != '\0') && (*units != '\n') && (*units != '\r')) { return PCMK__PARSE_INT_DEFAULT; } scan_ll(num_start, &msec, &end_text); if (msec > (LLONG_MAX / multiplier)) { // Arithmetics overflow while multiplier/divisor mutually exclusive return LLONG_MAX; } msec *= multiplier; msec /= divisor; return msec; } gboolean crm_is_true(const char *s) { gboolean ret = FALSE; if (s != NULL) { crm_str_to_boolean(s, &ret); } return ret; } int crm_str_to_boolean(const char *s, int *ret) { if (s == NULL) { return -1; } else if (strcasecmp(s, "true") == 0 || strcasecmp(s, "on") == 0 || strcasecmp(s, "yes") == 0 || strcasecmp(s, "y") == 0 || strcasecmp(s, "1") == 0) { *ret = TRUE; return 1; } else if (strcasecmp(s, "false") == 0 || strcasecmp(s, "off") == 0 || strcasecmp(s, "no") == 0 || strcasecmp(s, "n") == 0 || strcasecmp(s, "0") == 0) { *ret = FALSE; return 1; } return -1; } char * crm_strip_trailing_newline(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); } /* * This re-implements g_str_hash as it was prior to glib2-2.28: * * https://gitlab.gnome.org/GNOME/glib/commit/354d655ba8a54b754cb5a3efb42767327775696c * * Note that the new g_str_hash is presumably a *better* hash (it's actually * a correct implementation of DJB's hash), but we need to preserve existing * behaviour, because the hash key ultimately determines the "sort" order * when iterating through GHashTables, which affects allocation of scores to * clone instances when iterating through rsc->allowed_nodes. It (somehow) * also appears to have some minor impact on the ordering of a few * pseudo_event IDs in the transition graph. */ guint g_str_hash_traditional(gconstpointer v) { const signed char *p; guint32 h = 0; for (p = v; *p != '\0'; p++) h = (h << 5) - h + *p; return h; } /* used with hash tables where case does not matter */ gboolean crm_strcase_equal(gconstpointer a, gconstpointer b) { return pcmk__str_eq((const char *)a, (const char *)b, pcmk__str_casei); } guint crm_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; } static void copy_str_table_entry(gpointer key, gpointer value, gpointer user_data) { if (key && value && user_data) { g_hash_table_insert((GHashTable*)user_data, strdup(key), strdup(value)); } } GHashTable * crm_str_table_dup(GHashTable *old_table) { GHashTable *new_table = NULL; if (old_table) { new_table = crm_str_table_new(); 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 NULL) * \param[in,out] len If not NULL, must be set to length of \p list, * and will be updated to new length of \p list * \param[in] word String to add to \p list (\p list will be * unchanged if this is 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 This dynamically reallocates \p list as needed. \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(char **list, size_t *len, const char *word, const char *separator) { size_t orig_len, new_len; CRM_ASSERT(list != NULL); if (pcmk__str_empty(word)) { return; } // Use provided length, or calculate it if not available orig_len = (len != NULL)? *len : ((*list == NULL)? 0 : strlen(*list)); // Don't add a separator before the first word in the list if (orig_len == 0) { separator = ""; // Default to space-separated } else if (separator == NULL) { separator = " "; } new_len = orig_len + strlen(separator) + strlen(word); if (len != NULL) { *len = new_len; } // +1 for null terminator *list = pcmk__realloc(*list, new_len + 1); sprintf(*list + orig_len, "%s%s", separator, word); } /*! * \internal * \brief Compress data * * \param[in] data Data to compress * \param[in] length Number of characters of data to compress * \param[in] max Maximum size of compressed data (or 0 to estimate) * \param[out] result Where to store newly allocated compressed result * \param[out] result_len Where to store actual compressed length of result * * \return Standard Pacemaker return code */ int pcmk__compress(const char *data, unsigned int length, unsigned int max, char **result, unsigned int *result_len) { int rc; char *compressed = NULL; char *uncompressed = strdup(data); #ifdef CLOCK_MONOTONIC struct timespec after_t; struct timespec before_t; #endif if (max == 0) { max = (length * 1.01) + 601; // Size guaranteed to hold result } #ifdef CLOCK_MONOTONIC clock_gettime(CLOCK_MONOTONIC, &before_t); #endif compressed = calloc((size_t) max, sizeof(char)); CRM_ASSERT(compressed); *result_len = max; rc = BZ2_bzBuffToBuffCompress(compressed, result_len, uncompressed, length, CRM_BZ2_BLOCKS, 0, CRM_BZ2_WORK); free(uncompressed); if (rc != BZ_OK) { crm_err("Compression of %d bytes failed: %s " CRM_XS " bzerror=%d", length, bz2_strerror(rc), rc); free(compressed); return pcmk_rc_error; } #ifdef CLOCK_MONOTONIC clock_gettime(CLOCK_MONOTONIC, &after_t); crm_trace("Compressed %d bytes into %d (ratio %d:1) in %.0fms", length, *result_len, length / (*result_len), (after_t.tv_sec - before_t.tv_sec) * 1000 + (after_t.tv_nsec - before_t.tv_nsec) / 1e6); #else crm_trace("Compressed %d bytes into %d (ratio %d:1)", length, *result_len, length / (*result_len)); #endif *result = compressed; return pcmk_rc_ok; } char * crm_strdup_printf(char const *format, ...) { va_list ap; int len = 0; char *string = NULL; va_start(ap, format); len = vasprintf (&string, format, ap); CRM_ASSERT(len > 0); va_end(ap); return string; } int pcmk__parse_ll_range(const char *srcstring, long long *start, long long *end) { char *remainder = NULL; CRM_ASSERT(start != NULL && end != NULL); *start = PCMK__PARSE_INT_DEFAULT; *end = PCMK__PARSE_INT_DEFAULT; crm_trace("Attempting to decode: [%s]", srcstring); if (pcmk__str_empty(srcstring) || !strcmp(srcstring, "-")) { return pcmk_rc_unknown_format; } /* 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, &remainder); if (rc != pcmk_rc_ok || *remainder != '\0') { return pcmk_rc_unknown_format; } else { return pcmk_rc_ok; } } if (scan_ll(srcstring, start, &remainder) != pcmk_rc_ok) { return pcmk_rc_unknown_format; } if (*remainder && *remainder == '-') { if (*(remainder+1)) { char *more_remainder = NULL; int rc = scan_ll(remainder+1, end, &more_remainder); if (rc != pcmk_rc_ok || *more_remainder != '\0') { return pcmk_rc_unknown_format; } } } else if (*remainder && *remainder != '-') { *start = PCMK__PARSE_INT_DEFAULT; return pcmk_rc_unknown_format; } 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 * * Search \p lst for \p s, taking case into account. As a special case, * if "*" is the only element of \p lst, the search is successful. * * \param[in] lst List to search * \param[in] s String to search for * * \return \c TRUE if \p s is in \p lst, or \c FALSE otherwise */ gboolean pcmk__str_in_list(GList *lst, const gchar *s) { if (lst == NULL) { return FALSE; } if (strcmp(lst->data, "*") == 0 && lst->next == NULL) { return TRUE; } return g_list_find_custom(lst, s, (GCompareFunc) strcmp) != NULL; } static bool str_any_of(bool casei, const char *s, va_list args) { bool rc = false; if (s != NULL) { while (1) { const char *ele = va_arg(args, const char *); if (ele == NULL) { break; } else if (pcmk__str_eq(s, ele, casei? pcmk__str_casei : pcmk__str_none)) { rc = true; break; } } } 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 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(true, s, ap); 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(false, s, ap); va_end(ap); return rc; } /*! * \internal * \brief Check whether a character is in any of a list of strings * * \param[in] ch Character (ASCII) to search for * \param[in] ... Strings to search. Final argument must be * \c NULL. * * \return \c true if any of \p ... contain \p ch, \c false otherwise * \note \p ... must contain at least one argument (\c NULL). */ bool pcmk__char_in_any_str(int ch, ...) { bool rc = false; va_list ap; /* * Passing a char to va_start() can generate compiler warnings, * so ch is declared as an int. */ va_start(ap, ch); while (1) { const char *ele = va_arg(ap, const char *); if (ele == NULL) { break; } else if (strchr(ele, ch) != NULL) { rc = true; break; } } va_end(ap); return rc; } /*! * \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) { 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; } /* * \brief Sort strings. * * This is your one-stop function for string comparison. By default, this * function works like g_strcmp0. That is, like strcmp but a NULL string * sorts before a non-NULL string. * * Behavior can be changed with various flags: * * - pcmk__str_regex - The second string is a regular expression that the * first string will be matched against. * - pcmk__str_casei - By default, comparisons are done taking case into * account. This flag makes comparisons case-insensitive. * This can be combined with pcmk__str_regex. * - pcmk__str_null_matches - If one string is NULL and the other is not, * still return 0. * * \param[in] s1 First string to compare * \param[in] s2 Second string to compare, or a regular expression to * match if pcmk__str_regex is set * \param[in] flags A bitfield of pcmk__str_flags to modify operation * * \retval -1 \p s1 is NULL or comes before \p s2 * \retval 0 \p s1 and \p s2 are equal, or \p s1 is found in \p s2 if * pcmk__str_regex is set * \retval 1 \p s2 is NULL or \p s1 comes after \p s2, or if \p s2 * is an invalid regular expression, or \p s1 was not found * in \p s2 if 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 = calloc(1, sizeof(regex_t)); int reg_flags = REG_EXTENDED | REG_NOSUB; int regcomp_rc = 0; int rc = 0; if (s1 == NULL || s2 == NULL) { free(r_patt); 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); if (rc != 0) { rc = 1; } } regfree(r_patt); free(r_patt); 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 (pcmk_is_set(flags, pcmk__str_casei)) { return strcasecmp(s1, s2); } else { return strcmp(s1, s2); } } // Deprecated functions kept only for backward API compatibility #include gboolean safe_str_neq(const char *a, const char *b) { if (a == b) { return FALSE; } else if (a == NULL || b == NULL) { return TRUE; } else if (strcasecmp(a, b) == 0) { return FALSE; } return TRUE; } gboolean crm_str_eq(const char *a, const char *b, gboolean use_case) { if (use_case) { return g_strcmp0(a, b) == 0; /* TODO - Figure out which calls, if any, really need to be case independent */ } else if (a == b) { return TRUE; } else if (a == NULL || b == NULL) { /* shouldn't be comparing NULLs */ return FALSE; } else if (strcasecmp(a, b) == 0) { return TRUE; } return FALSE; } // End deprecated API