Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F2824981
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
34 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/devel/Makefile.am b/devel/Makefile.am
index 9d3c0884c4..d520c21ed8 100644
--- a/devel/Makefile.am
+++ b/devel/Makefile.am
@@ -1,58 +1,57 @@
#
# Copyright 2020 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.
#
EXTRA_SCRIPTS = coccinelle/test/testrunner.sh
EXTRA_DIST = $(EXTRA_SCRIPTS) \
coccinelle/ref-passed-variables-inited.cocci \
coccinelle/string-empty.cocci \
coccinelle/string-null-matches.cocci \
coccinelle/string-replacements.cocci \
coccinelle/test/ref-passed-variables-inited.input.c \
coccinelle/test/ref-passed-variables-inited.output
# Coccinelle is a tool that takes special patch-like files (called semantic patches) and
# applies them throughout a source tree. This is useful when refactoring, changing APIs,
# catching dangerous or incorrect code, and other similar tasks. It's not especially
# easy to write a semantic patch but most users should only be concerned about running
# the target and inspecting the results.
#
# Documentation (including examples, which are the most useful):
# http://coccinelle.lip6.fr/documentation.php
#
# Run the "make cocci" target to just output what would be done, or "make cocci-inplace"
# to apply the changes to the source tree.
#
# COCCI_FILES may be set on the command line, if you want to test just a single file
# while it's under development. Otherwise, it is a list of all the files that are ready
# to be run.
#
# ref-passed-variables-inited.cocci seems to be returning some false positives around
# GHashTableIters, so it is disabled for the moment.
COCCI_FILES ?= coccinelle/string-empty.cocci \
- coccinelle/string-null-matches.cocci \
- coccinelle/string-replacements.cocci
+ coccinelle/string-null-matches.cocci
cocci:
for f in $(COCCI_FILES); do \
for d in daemons include lib tools; do \
test $$d = "include" \
&& spatch $(_SPATCH_FLAGS) --include-headers --local-includes \
--preprocess --sp-file $$f --dir ../$$d \
|| spatch $(_SPATCH_FLAGS) --local-includes \
--preprocess --sp-file $$f --dir ../$$d; \
done; \
done
cocci-inplace:
$(MAKE) $(AM_MAKEFLAGS) _SPATCH_FLAGS=--in-place cocci
cocci-test:
for f in coccinelle/test/*.c; do \
coccinelle/test/testrunner.sh $$f; \
done
diff --git a/include/crm/common/util.h b/include/crm/common/util.h
index 1a69d761f1..6cfd997901 100644
--- a/include/crm/common/util.h
+++ b/include/crm/common/util.h
@@ -1,229 +1,235 @@
/*
* Copyright 2004-2020 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_COMMON_UTIL__H
# define CRM_COMMON_UTIL__H
#ifdef __cplusplus
extern "C" {
#endif
/**
* \file
* \brief Utility functions
* \ingroup core
*/
# include <sys/types.h> // gid_t, mode_t, size_t, time_t, uid_t
# include <stdlib.h>
# include <stdbool.h>
# include <stdint.h> // uint32_t
# include <limits.h>
# include <signal.h>
# include <glib.h>
# include <libxml/tree.h>
# include <crm/lrmd.h>
# include <crm/common/acl.h>
# include <crm/common/results.h>
# define ONLINESTATUS "online" // Status of an online client
# define OFFLINESTATUS "offline" // Status of an offline client
// public name/value pair functions (from nvpair.c)
int pcmk_scan_nvpair(const char *input, char **name, char **value);
char *pcmk_format_nvpair(const char *name, const char *value, const char *units);
char *pcmk_format_named_time(const char *name, time_t epoch_time);
/* public Pacemaker Remote functions (from remote.c) */
int crm_default_remote_port(void);
/* public string functions (from strings.c) */
char *crm_itoa_stack(int an_int, char *buf, size_t len);
gboolean crm_is_true(const char *s);
int crm_str_to_boolean(const char *s, int *ret);
long long crm_parse_ll(const char *text, const char *default_text);
int crm_parse_int(const char *text, const char *default_text);
long long crm_get_msec(const char *input);
char * crm_strip_trailing_newline(char *str);
-gboolean crm_str_eq(const char *a, const char *b, gboolean use_case);
-gboolean safe_str_neq(const char *a, const char *b);
gboolean crm_strcase_equal(gconstpointer a, gconstpointer b);
guint crm_strcase_hash(gconstpointer v);
guint g_str_hash_traditional(gconstpointer v);
char *crm_strdup_printf(char const *format, ...) __attribute__ ((__format__ (__printf__, 1, 2)));
int pcmk_numeric_strcasecmp(const char *s1, const char *s2);
-# define safe_str_eq(a, b) pcmk__str_eq(a, b, pcmk__str_casei)
# define crm_str_hash g_str_hash_traditional
static inline char *
crm_itoa(int an_int)
{
return crm_strdup_printf("%d", an_int);
}
static inline char *
crm_ftoa(double a_float)
{
return crm_strdup_printf("%f", a_float);
}
static inline char *
crm_ttoa(time_t epoch_time)
{
return crm_strdup_printf("%lld", (long long) epoch_time);
}
/*!
* \brief Create hash table with dynamically allocated string keys/values
*
* \return Newly allocated hash table
* \note It is the caller's responsibility to free the result, using
* g_hash_table_destroy().
*/
static inline GHashTable *
crm_str_table_new(void)
{
return g_hash_table_new_full(crm_str_hash, g_str_equal, free, free);
}
/*!
* \brief Create hash table with case-insensitive dynamically allocated string keys/values
*
* \return Newly allocated hash table
* \note It is the caller's responsibility to free the result, using
* g_hash_table_destroy().
*/
static inline GHashTable *
crm_strcase_table_new(void)
{
return g_hash_table_new_full(crm_strcase_hash, crm_strcase_equal, free, free);
}
GHashTable *crm_str_table_dup(GHashTable *old_table);
# define crm_atoi(text, default_text) crm_parse_int(text, default_text)
/* public I/O functions (from io.c) */
void crm_build_path(const char *path_c, mode_t mode);
guint crm_parse_interval_spec(const char *input);
int char2score(const char *score);
char *score2char(int score);
char *score2char_stack(int score, char *buf, size_t len);
/* public operation functions (from operations.c) */
gboolean parse_op_key(const char *key, char **rsc_id, char **op_type,
guint *interval_ms);
gboolean decode_transition_key(const char *key, char **uuid, int *transition_id,
int *action_id, int *target_rc);
gboolean decode_transition_magic(const char *magic, char **uuid,
int *transition_id, int *action_id,
int *op_status, int *op_rc, int *target_rc);
int rsc_op_expected_rc(lrmd_event_data_t *event);
gboolean did_rsc_op_fail(lrmd_event_data_t *event, int target_rc);
bool crm_op_needs_metadata(const char *rsc_class, const char *op);
xmlNode *crm_create_op_xml(xmlNode *parent, const char *prefix,
const char *task, const char *interval_spec,
const char *timeout);
#define CRM_DEFAULT_OP_TIMEOUT_S "20s"
// Public resource agent functions (from agents.c)
// Capabilities supported by a resource agent standard
enum pcmk_ra_caps {
pcmk_ra_cap_none = 0,
pcmk_ra_cap_provider = (1 << 0), // Requires provider
pcmk_ra_cap_status = (1 << 1), // Supports status instead of monitor
pcmk_ra_cap_params = (1 << 2), // Supports parameters
pcmk_ra_cap_unique = (1 << 3), // Supports unique clones
pcmk_ra_cap_promotable = (1 << 4), // Supports promotable clones
pcmk_ra_cap_stdin = (1 << 5), // Reads from standard input
};
uint32_t pcmk_get_ra_caps(const char *standard);
char *crm_generate_ra_key(const char *standard, const char *provider,
const char *type);
int crm_parse_agent_spec(const char *spec, char **standard, char **provider,
char **type);
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);
static inline gboolean
is_not_set(long long word, long long bit)
{
return ((word & bit) == 0);
}
static inline gboolean
is_set(long long word, long long bit)
{
return ((word & bit) == bit);
}
static inline gboolean
is_set_any(long long word, long long bit)
{
return ((word & bit) != 0);
}
static inline guint
crm_hash_table_size(GHashTable * hashtable)
{
if (hashtable == NULL) {
return 0;
}
return g_hash_table_size(hashtable);
}
char *crm_meta_name(const char *field);
const char *crm_meta_value(GHashTable * hash, const char *field);
char *crm_md5sum(const char *buffer);
char *crm_generate_uuid(void);
bool crm_is_daemon_name(const char *name);
int crm_user_lookup(const char *name, uid_t * uid, gid_t * gid);
int pcmk_daemon_user(uid_t *uid, gid_t *gid);
#ifdef HAVE_GNUTLS_GNUTLS_H
void crm_gnutls_global_init(void);
#endif
char *pcmk_hostname(void);
bool pcmk_str_is_infinity(const char *s);
bool pcmk_str_is_minus_infinity(const char *s);
#ifndef PCMK__NO_COMPAT
/* Everything here is deprecated and kept only for public API backward
* compatibility. It will be moved to compatibility.h in a future release.
*/
//! \deprecated Use crm_parse_interval_spec() instead
#define crm_get_interval crm_parse_interval_spec
//! \deprecated Use pcmk_get_ra_caps() instead
bool crm_provider_required(const char *standard);
+//! \deprecated Use pcmk__str_eq() instead
+gboolean crm_str_eq(const char *a, const char *b, gboolean use_case);
+
+//! \deprecated Use pcmk__str_eq() instead
+gboolean safe_str_neq(const char *a, const char *b);
+
+//! \deprecated Use pcmk__str_eq() instead
+#define safe_str_eq(a, b) pcmk__str_eq(a, b, pcmk__str_casei)
+
#endif // PCMK__NO_COMPAT
#ifdef __cplusplus
}
#endif
#endif
diff --git a/lib/common/strings.c b/lib/common/strings.c
index 481ba07591..8674f5ed83 100644
--- a/lib/common/strings.c
+++ b/lib/common/strings.c
@@ -1,852 +1,860 @@
/*
* Copyright 2004-2020 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <regex.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <limits.h>
#include <bzlib.h>
#include <sys/types.h>
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 (also set errno on error)
*/
static int
scan_ll(const char *text, long long *result, char **end_text)
{
long long local_result = -1;
char *local_end_text = NULL;
int rc = pcmk_rc_ok;
errno = 0;
if (text != NULL) {
#ifdef ANSI_ONLY
local_result = (long long) strtol(text, &local_end_text, 10);
#else
local_result = strtoll(text, &local_end_text, 10);
#endif
if (errno == ERANGE) {
rc = errno;
crm_warn("Integer parsed from %s was clipped to %lld",
text, local_result);
} else if (errno != 0) {
rc = errno;
local_result = -1;
crm_err("Could not parse integer from %s (using -1 instead): %s",
text, pcmk_rc_str(rc));
} else if (local_end_text == text) {
rc = EINVAL;
local_result = -1;
crm_err("Could not parse integer from %s (using -1 instead): "
"No digits found", text);
}
if ((end_text == NULL) && (local_end_text != NULL)
&& (local_end_text[0] != '\0')) {
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, -1 (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 -1;
}
}
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 -1 (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 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 -1 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 = -1;
size_t num_len = 0;
char *end_text = NULL;
if (input == NULL) {
return -1;
}
num_start = input + strspn(input, WHITESPACE);
num_len = strspn(num_start, NUMCHARS);
if (num_len < 1) {
return -1;
}
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')) {
return -1;
}
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
-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_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;
}
-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;
-}
-
/*!
* \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 space-separated string list
*
* \param[in,out] list Pointer to beginning of list
* \param[in] word Word to add to list
*
* \return (Potentially new) beginning of list
* \note This dynamically reallocates list as needed.
*/
char *
pcmk__add_word(char *list, const char *word)
{
if (word != NULL) {
size_t len = list? strlen(list) : 0;
list = realloc_safe(list, len + strlen(word) + 2); // 2 = space + EOS
sprintf(list + len, " %s", word);
}
return list;
}
/*!
* \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 = -1;
*end = -1;
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 = -1;
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;
}
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;
}
bool
pcmk__str_any_of(const char *s, ...)
{
bool rc = false;
va_list ap;
va_start(ap, s);
while (1) {
const char *ele = va_arg(ap, const char *);
if (ele == NULL) {
break;
} else if (pcmk__str_eq(s, ele, pcmk__str_casei)) {
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 (is_set(flags, pcmk__str_regex)) {
regex_t *r_patt = calloc(1, sizeof(regex_t));
int reg_flags = REG_EXTENDED | REG_NOSUB | (is_set(flags, pcmk__str_casei) ? REG_ICASE : 0);
int regcomp_rc = 0;
int rc = 0;
if (s1 == NULL || s2 == NULL) {
free(r_patt);
return 1;
}
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 (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 (is_set(flags, pcmk__str_casei)) {
return strcasecmp(s1, s2);
} else {
return strcmp(s1, s2);
}
}
+
+// Deprecated functions kept only for backward API compatibility
+
+gboolean safe_str_neq(const char *a, const char *b);
+
+gboolean crm_str_eq(const char *a, const char *b, gboolean use_case);
+
+//! \deprecated Use pcmk__str_eq() instead
+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;
+}
+
+//! \deprecated Use pcmk__str_eq() instead
+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;
+}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Jan 25, 11:08 AM (1 d, 3 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1322278
Default Alt Text
(34 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment