pacemaker  2.0.4-2deceaa
Scalable High-Availability cluster resource manager
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
strings.c
Go to the documentation of this file.
1 /*
2  * Copyright 2004-2020 the Pacemaker project contributors
3  *
4  * The version control history for this file may have further details.
5  *
6  * This source code is licensed under the GNU Lesser General Public License
7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8  */
9 
10 #include <crm_internal.h>
11 
12 #ifndef _GNU_SOURCE
13 # define _GNU_SOURCE
14 #endif
15 
16 #include <stdio.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <limits.h>
20 #include <bzlib.h>
21 #include <sys/types.h>
22 
23 char *
24 crm_itoa_stack(int an_int, char *buffer, size_t len)
25 {
26  if (buffer != NULL) {
27  snprintf(buffer, len, "%d", an_int);
28  }
29 
30  return buffer;
31 }
32 
43 static int
44 scan_ll(const char *text, long long *result, char **end_text)
45 {
46  long long local_result = -1;
47  char *local_end_text = NULL;
48  int rc = pcmk_rc_ok;
49 
50  errno = 0;
51  if (text != NULL) {
52 #ifdef ANSI_ONLY
53  local_result = (long long) strtol(text, &local_end_text, 10);
54 #else
55  local_result = strtoll(text, &local_end_text, 10);
56 #endif
57  if (errno == ERANGE) {
58  rc = errno;
59  crm_warn("Integer parsed from %s was clipped to %lld",
60  text, local_result);
61 
62  } else if (errno != 0) {
63  rc = errno;
64  local_result = -1;
65  crm_err("Could not parse integer from %s (using -1 instead): %s",
66  text, pcmk_rc_str(rc));
67 
68  } else if (local_end_text == text) {
69  rc = EINVAL;
70  local_result = -1;
71  crm_err("Could not parse integer from %s (using -1 instead): "
72  "No digits found", text);
73  }
74 
75  if ((end_text == NULL) && (local_end_text != NULL)
76  && (local_end_text[0] != '\0')) {
77  crm_warn("Characters left over after parsing '%s': '%s'",
78  text, local_end_text);
79  }
80  errno = rc;
81  }
82  if (end_text != NULL) {
83  *end_text = local_end_text;
84  }
85  if (result != NULL) {
86  *result = local_result;
87  }
88  return rc;
89 }
90 
99 long long
100 crm_parse_ll(const char *text, const char *default_text)
101 {
102  long long result;
103 
104  if (text == NULL) {
105  text = default_text;
106  if (text == NULL) {
107  crm_err("No default conversion value supplied");
108  errno = EINVAL;
109  return -1;
110  }
111  }
112  scan_ll(text, &result, NULL);
113  return result;
114 }
115 
125 int
126 crm_parse_int(const char *text, const char *default_text)
127 {
128  long long result = crm_parse_ll(text, default_text);
129 
130  if (result < INT_MIN) {
131  // If errno is ERANGE, crm_parse_ll() has already logged a message
132  if (errno != ERANGE) {
133  crm_err("Conversion of %s was clipped: %lld", text, result);
134  errno = ERANGE;
135  }
136  return INT_MIN;
137 
138  } else if (result > INT_MAX) {
139  // If errno is ERANGE, crm_parse_ll() has already logged a message
140  if (errno != ERANGE) {
141  crm_err("Conversion of %s was clipped: %lld", text, result);
142  errno = ERANGE;
143  }
144  return INT_MAX;
145  }
146 
147  return (int) result;
148 }
149 
161 int
162 pcmk__guint_from_hash(GHashTable *table, const char *key, guint default_val,
163  guint *result)
164 {
165  const char *value;
166  long long value_ll;
167 
168  CRM_CHECK((table != NULL) && (key != NULL), return EINVAL);
169 
170  value = g_hash_table_lookup(table, key);
171  if (value == NULL) {
172  if (result != NULL) {
173  *result = default_val;
174  }
175  return pcmk_rc_ok;
176  }
177 
178  errno = 0;
179  value_ll = crm_parse_ll(value, NULL);
180  if (errno != 0) {
181  return errno; // Message already logged
182  }
183  if ((value_ll < 0) || (value_ll > G_MAXUINT)) {
184  crm_warn("Could not parse non-negative integer from %s", value);
185  return ERANGE;
186  }
187 
188  if (result != NULL) {
189  *result = (guint) value_ll;
190  }
191  return pcmk_rc_ok;
192 }
193 
194 #ifndef NUMCHARS
195 # define NUMCHARS "0123456789."
196 #endif
197 
198 #ifndef WHITESPACE
199 # define WHITESPACE " \t\n\r\f"
200 #endif
201 
210 long long
211 crm_get_msec(const char *input)
212 {
213  const char *num_start = NULL;
214  const char *units;
215  long long multiplier = 1000;
216  long long divisor = 1;
217  long long msec = -1;
218  size_t num_len = 0;
219  char *end_text = NULL;
220 
221  if (input == NULL) {
222  return -1;
223  }
224 
225  num_start = input + strspn(input, WHITESPACE);
226  num_len = strspn(num_start, NUMCHARS);
227  if (num_len < 1) {
228  return -1;
229  }
230  units = num_start + num_len;
231  units += strspn(units, WHITESPACE);
232 
233  if (!strncasecmp(units, "ms", 2) || !strncasecmp(units, "msec", 4)) {
234  multiplier = 1;
235  divisor = 1;
236  } else if (!strncasecmp(units, "us", 2) || !strncasecmp(units, "usec", 4)) {
237  multiplier = 1;
238  divisor = 1000;
239  } else if (!strncasecmp(units, "s", 1) || !strncasecmp(units, "sec", 3)) {
240  multiplier = 1000;
241  divisor = 1;
242  } else if (!strncasecmp(units, "m", 1) || !strncasecmp(units, "min", 3)) {
243  multiplier = 60 * 1000;
244  divisor = 1;
245  } else if (!strncasecmp(units, "h", 1) || !strncasecmp(units, "hr", 2)) {
246  multiplier = 60 * 60 * 1000;
247  divisor = 1;
248  } else if ((*units != EOS) && (*units != '\n') && (*units != '\r')) {
249  return -1;
250  }
251 
252  scan_ll(num_start, &msec, &end_text);
253  if (msec > (LLONG_MAX / multiplier)) {
254  // Arithmetics overflow while multiplier/divisor mutually exclusive
255  return LLONG_MAX;
256  }
257  msec *= multiplier;
258  msec /= divisor;
259  return msec;
260 }
261 
262 gboolean
263 safe_str_neq(const char *a, const char *b)
264 {
265  if (a == b) {
266  return FALSE;
267 
268  } else if (a == NULL || b == NULL) {
269  return TRUE;
270 
271  } else if (strcasecmp(a, b) == 0) {
272  return FALSE;
273  }
274  return TRUE;
275 }
276 
277 gboolean
278 crm_is_true(const char *s)
279 {
280  gboolean ret = FALSE;
281 
282  if (s != NULL) {
283  crm_str_to_boolean(s, &ret);
284  }
285  return ret;
286 }
287 
288 int
289 crm_str_to_boolean(const char *s, int *ret)
290 {
291  if (s == NULL) {
292  return -1;
293 
294  } else if (strcasecmp(s, "true") == 0
295  || strcasecmp(s, "on") == 0
296  || strcasecmp(s, "yes") == 0 || strcasecmp(s, "y") == 0 || strcasecmp(s, "1") == 0) {
297  *ret = TRUE;
298  return 1;
299 
300  } else if (strcasecmp(s, "false") == 0
301  || strcasecmp(s, "off") == 0
302  || strcasecmp(s, "no") == 0 || strcasecmp(s, "n") == 0 || strcasecmp(s, "0") == 0) {
303  *ret = FALSE;
304  return 1;
305  }
306  return -1;
307 }
308 
309 char *
311 {
312  int len;
313 
314  if (str == NULL) {
315  return str;
316  }
317 
318  for (len = strlen(str) - 1; len >= 0 && str[len] == '\n'; len--) {
319  str[len] = '\0';
320  }
321 
322  return str;
323 }
324 
325 gboolean
326 crm_str_eq(const char *a, const char *b, gboolean use_case)
327 {
328  if (use_case) {
329  return g_strcmp0(a, b) == 0;
330 
331  /* TODO - Figure out which calls, if any, really need to be case independent */
332  } else if (a == b) {
333  return TRUE;
334 
335  } else if (a == NULL || b == NULL) {
336  /* shouldn't be comparing NULLs */
337  return FALSE;
338 
339  } else if (strcasecmp(a, b) == 0) {
340  return TRUE;
341  }
342  return FALSE;
343 }
344 
357 bool
358 pcmk__starts_with(const char *str, const char *prefix)
359 {
360  const char *s = str;
361  const char *p = prefix;
362 
363  if (!s || !p) {
364  return false;
365  }
366  while (*s && *p) {
367  if (*s++ != *p++) {
368  return false;
369  }
370  }
371  return (*p == 0);
372 }
373 
374 static inline bool
375 ends_with(const char *s, const char *match, bool as_extension)
376 {
377  if (pcmk__str_empty(match)) {
378  return true;
379  } else if (s == NULL) {
380  return false;
381  } else {
382  size_t slen, mlen;
383 
384  /* Besides as_extension, we could also check
385  !strchr(&match[1], match[0]) but that would be inefficient.
386  */
387  if (as_extension) {
388  s = strrchr(s, match[0]);
389  return (s == NULL)? false : !strcmp(s, match);
390  }
391 
392  mlen = strlen(match);
393  slen = strlen(s);
394  return ((slen >= mlen) && !strcmp(s + slen - mlen, match));
395  }
396 }
397 
409 bool
410 pcmk__ends_with(const char *s, const char *match)
411 {
412  return ends_with(s, match, false);
413 }
414 
436 bool
437 pcmk__ends_with_ext(const char *s, const char *match)
438 {
439  return ends_with(s, match, true);
440 }
441 
442 /*
443  * This re-implements g_str_hash as it was prior to glib2-2.28:
444  *
445  * https://gitlab.gnome.org/GNOME/glib/commit/354d655ba8a54b754cb5a3efb42767327775696c
446  *
447  * Note that the new g_str_hash is presumably a *better* hash (it's actually
448  * a correct implementation of DJB's hash), but we need to preserve existing
449  * behaviour, because the hash key ultimately determines the "sort" order
450  * when iterating through GHashTables, which affects allocation of scores to
451  * clone instances when iterating through rsc->allowed_nodes. It (somehow)
452  * also appears to have some minor impact on the ordering of a few
453  * pseudo_event IDs in the transition graph.
454  */
455 guint
456 g_str_hash_traditional(gconstpointer v)
457 {
458  const signed char *p;
459  guint32 h = 0;
460 
461  for (p = v; *p != '\0'; p++)
462  h = (h << 5) - h + *p;
463 
464  return h;
465 }
466 
467 /* used with hash tables where case does not matter */
468 gboolean
469 crm_strcase_equal(gconstpointer a, gconstpointer b)
470 {
471  return crm_str_eq((const char *) a, (const char *) b, FALSE);
472 }
473 
474 guint
475 crm_strcase_hash(gconstpointer v)
476 {
477  const signed char *p;
478  guint32 h = 0;
479 
480  for (p = v; *p != '\0'; p++)
481  h = (h << 5) - h + g_ascii_tolower(*p);
482 
483  return h;
484 }
485 
486 static void
487 copy_str_table_entry(gpointer key, gpointer value, gpointer user_data)
488 {
489  if (key && value && user_data) {
490  g_hash_table_insert((GHashTable*)user_data, strdup(key), strdup(value));
491  }
492 }
493 
494 GHashTable *
495 crm_str_table_dup(GHashTable *old_table)
496 {
497  GHashTable *new_table = NULL;
498 
499  if (old_table) {
500  new_table = crm_str_table_new();
501  g_hash_table_foreach(old_table, copy_str_table_entry, new_table);
502  }
503  return new_table;
504 }
505 
516 char *
517 pcmk__add_word(char *list, const char *word)
518 {
519  if (word != NULL) {
520  size_t len = list? strlen(list) : 0;
521 
522  list = realloc_safe(list, len + strlen(word) + 2); // 2 = space + EOS
523  sprintf(list + len, " %s", word);
524  }
525  return list;
526 }
527 
540 int
541 pcmk__compress(const char *data, unsigned int length, unsigned int max,
542  char **result, unsigned int *result_len)
543 {
544  int rc;
545  char *compressed = NULL;
546  char *uncompressed = strdup(data);
547 #ifdef CLOCK_MONOTONIC
548  struct timespec after_t;
549  struct timespec before_t;
550 #endif
551 
552  if (max == 0) {
553  max = (length * 1.01) + 601; // Size guaranteed to hold result
554  }
555 
556 #ifdef CLOCK_MONOTONIC
557  clock_gettime(CLOCK_MONOTONIC, &before_t);
558 #endif
559 
560  compressed = calloc((size_t) max, sizeof(char));
561  CRM_ASSERT(compressed);
562 
563  *result_len = max;
564  rc = BZ2_bzBuffToBuffCompress(compressed, result_len, uncompressed, length,
566  free(uncompressed);
567  if (rc != BZ_OK) {
568  crm_err("Compression of %d bytes failed: %s " CRM_XS " bzerror=%d",
569  length, bz2_strerror(rc), rc);
570  free(compressed);
571  return pcmk_rc_error;
572  }
573 
574 #ifdef CLOCK_MONOTONIC
575  clock_gettime(CLOCK_MONOTONIC, &after_t);
576 
577  crm_trace("Compressed %d bytes into %d (ratio %d:1) in %.0fms",
578  length, *result_len, length / (*result_len),
579  (after_t.tv_sec - before_t.tv_sec) * 1000 +
580  (after_t.tv_nsec - before_t.tv_nsec) / 1e6);
581 #else
582  crm_trace("Compressed %d bytes into %d (ratio %d:1)",
583  length, *result_len, length / (*result_len));
584 #endif
585 
586  *result = compressed;
587  return pcmk_rc_ok;
588 }
589 
590 char *
591 crm_strdup_printf(char const *format, ...)
592 {
593  va_list ap;
594  int len = 0;
595  char *string = NULL;
596 
597  va_start(ap, format);
598  len = vasprintf (&string, format, ap);
599  CRM_ASSERT(len > 0);
600  va_end(ap);
601  return string;
602 }
603 
604 int
605 pcmk__parse_ll_range(const char *srcstring, long long *start, long long *end)
606 {
607  char *remainder = NULL;
608 
609  CRM_ASSERT(start != NULL && end != NULL);
610 
611  *start = -1;
612  *end = -1;
613 
614  crm_trace("Attempting to decode: [%s]", srcstring);
615  if (srcstring == NULL || strcmp(srcstring, "") == 0 || strcmp(srcstring, "-") == 0) {
616  return pcmk_rc_unknown_format;
617  }
618 
619  /* String starts with a dash, so this is either a range with
620  * no beginning or garbage.
621  * */
622  if (*srcstring == '-') {
623  int rc = scan_ll(srcstring+1, end, &remainder);
624 
625  if (rc != pcmk_rc_ok || *remainder != '\0') {
626  return pcmk_rc_unknown_format;
627  } else {
628  return pcmk_rc_ok;
629  }
630  }
631 
632  if (scan_ll(srcstring, start, &remainder) != pcmk_rc_ok) {
633  return pcmk_rc_unknown_format;
634  }
635 
636  if (*remainder && *remainder == '-') {
637  if (*(remainder+1)) {
638  char *more_remainder = NULL;
639  int rc = scan_ll(remainder+1, end, &more_remainder);
640 
641  if (rc != pcmk_rc_ok || *more_remainder != '\0') {
642  return pcmk_rc_unknown_format;
643  }
644  }
645  } else if (*remainder && *remainder != '-') {
646  *start = -1;
647  return pcmk_rc_unknown_format;
648  } else {
649  /* The input string contained only one number. Set start and end
650  * to the same value and return pcmk_rc_ok. This gives the caller
651  * a way to tell this condition apart from a range with no end.
652  */
653  *end = *start;
654  }
655 
656  return pcmk_rc_ok;
657 }
658 
659 gboolean
660 pcmk__str_in_list(GList *lst, const gchar *s)
661 {
662  if (lst == NULL) {
663  return FALSE;
664  }
665 
666  if (strcmp(lst->data, "*") == 0 && lst->next == NULL) {
667  return TRUE;
668  }
669 
670  return g_list_find_custom(lst, s, (GCompareFunc) strcmp) != NULL;
671 }
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:233
const char * bz2_strerror(int rc)
Definition: results.c:718
gboolean safe_str_neq(const char *a, const char *b)
Definition: strings.c:263
gboolean pcmk__str_in_list(GList *lst, const gchar *s)
Definition: strings.c:660
guint g_str_hash_traditional(gconstpointer v)
Definition: strings.c:456
bool pcmk__ends_with_ext(const char *s, const char *match)
Definition: strings.c:437
int pcmk__guint_from_hash(GHashTable *table, const char *key, guint default_val, guint *result)
Definition: strings.c:162
long long crm_get_msec(const char *input)
Parse a time+units string and return milliseconds equivalent.
Definition: strings.c:211
int crm_parse_int(const char *text, const char *default_text)
Parse an integer value from a string.
Definition: strings.c:126
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
Definition: results.c:413
#define CRM_BZ2_WORK
Definition: xml.h:46
#define crm_warn(fmt, args...)
Definition: logging.h:364
int rc
Definition: pcmk_fence.c:34
#define crm_trace(fmt, args...)
Definition: logging.h:369
guint crm_strcase_hash(gconstpointer v)
Definition: strings.c:475
int pcmk__compress(const char *data, unsigned int length, unsigned int max, char **result, unsigned int *result_len)
Definition: strings.c:541
#define EOS
Definition: crm.h:56
gboolean crm_str_eq(const char *a, const char *b, gboolean use_case)
Definition: strings.c:326
char * crm_itoa_stack(int an_int, char *buf, size_t len)
Definition: strings.c:24
#define CRM_XS
Definition: logging.h:54
int crm_str_to_boolean(const char *s, int *ret)
Definition: strings.c:289
long long crm_parse_ll(const char *text, const char *default_text)
Parse a long long integer value from a string.
Definition: strings.c:100
#define crm_err(fmt, args...)
Definition: logging.h:363
bool pcmk__ends_with(const char *s, const char *match)
Definition: strings.c:410
#define CRM_ASSERT(expr)
Definition: results.h:42
GHashTable * crm_str_table_dup(GHashTable *old_table)
Definition: strings.c:495
char * pcmk__add_word(char *list, const char *word)
Definition: strings.c:517
char data[0]
Definition: internal.h:90
gboolean crm_is_true(const char *s)
Definition: strings.c:278
#define CRM_BZ2_BLOCKS
Definition: xml.h:45
char * crm_strip_trailing_newline(char *str)
Definition: strings.c:310
bool pcmk__starts_with(const char *str, const char *prefix)
Check whether a string starts with a certain sequence.
Definition: strings.c:358
#define WHITESPACE
Definition: strings.c:199
char * crm_strdup_printf(char const *format,...) __attribute__((__format__(__printf__
char int pcmk__parse_ll_range(const char *srcstring, long long *start, long long *end)
Definition: strings.c:605
#define NUMCHARS
Definition: strings.c:195
gboolean crm_strcase_equal(gconstpointer a, gconstpointer b)
Definition: strings.c:469