root/lib/common/utils.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. crm_user_lookup
  2. pcmk_daemon_user
  3. version_helper
  4. compare_version
  5. crm_parse_interval_spec
  6. log_assertion_as
  7. abort_as
  8. fail_assert_as
  9. crm_abort
  10. pcmk__daemonize
  11. crm_meta_name
  12. crm_meta_value
  13. crm_generate_uuid
  14. crm_gnutls_global_init
  15. pcmk_hostname
  16. pcmk_str_is_infinity
  17. pcmk_str_is_minus_infinity
  18. pcmk__sleep_ms

   1 /*
   2  * Copyright 2004-2022 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 <sys/types.h>
  17 #include <sys/wait.h>
  18 #include <sys/stat.h>
  19 #include <sys/utsname.h>
  20 
  21 #include <stdio.h>
  22 #include <unistd.h>
  23 #include <string.h>
  24 #include <stdlib.h>
  25 #include <limits.h>
  26 #include <pwd.h>
  27 #include <time.h>
  28 #include <libgen.h>
  29 #include <signal.h>
  30 
  31 #include <qb/qbdefs.h>
  32 
  33 #include <crm/crm.h>
  34 #include <crm/services.h>
  35 #include <crm/msg_xml.h>
  36 #include <crm/cib/internal.h>
  37 #include <crm/common/xml.h>
  38 #include <crm/common/util.h>
  39 #include <crm/common/ipc.h>
  40 #include <crm/common/iso8601.h>
  41 #include <crm/common/mainloop.h>
  42 #include <libxml2/libxml/relaxng.h>
  43 
  44 #include "crmcommon_private.h"
  45 
  46 #ifndef PW_BUFFER_LEN
  47 #  define PW_BUFFER_LEN         500
  48 #endif
  49 
  50 CRM_TRACE_INIT_DATA(common);
  51 
  52 gboolean crm_config_error = FALSE;
  53 gboolean crm_config_warning = FALSE;
  54 char *crm_system_name = NULL;
  55 
  56 int
  57 crm_user_lookup(const char *name, uid_t * uid, gid_t * gid)
     /* [previous][next][first][last][top][bottom][index][help] */
  58 {
  59     int rc = pcmk_ok;
  60     char *buffer = NULL;
  61     struct passwd pwd;
  62     struct passwd *pwentry = NULL;
  63 
  64     buffer = calloc(1, PW_BUFFER_LEN);
  65     if (buffer == NULL) {
  66         return -ENOMEM;
  67     }
  68 
  69     rc = getpwnam_r(name, &pwd, buffer, PW_BUFFER_LEN, &pwentry);
  70     if (pwentry) {
  71         if (uid) {
  72             *uid = pwentry->pw_uid;
  73         }
  74         if (gid) {
  75             *gid = pwentry->pw_gid;
  76         }
  77         crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid);
  78 
  79     } else {
  80         rc = rc? -rc : -EINVAL;
  81         crm_info("User %s lookup: %s", name, pcmk_strerror(rc));
  82     }
  83 
  84     free(buffer);
  85     return rc;
  86 }
  87 
  88 /*!
  89  * \brief Get user and group IDs of pacemaker daemon user
  90  *
  91  * \param[out] uid  If non-NULL, where to store daemon user ID
  92  * \param[out] gid  If non-NULL, where to store daemon group ID
  93  *
  94  * \return pcmk_ok on success, -errno otherwise
  95  */
  96 int
  97 pcmk_daemon_user(uid_t *uid, gid_t *gid)
     /* [previous][next][first][last][top][bottom][index][help] */
  98 {
  99     static uid_t daemon_uid;
 100     static gid_t daemon_gid;
 101     static bool found = false;
 102     int rc = pcmk_ok;
 103 
 104     if (!found) {
 105         rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid);
 106         if (rc == pcmk_ok) {
 107             found = true;
 108         }
 109     }
 110     if (found) {
 111         if (uid) {
 112             *uid = daemon_uid;
 113         }
 114         if (gid) {
 115             *gid = daemon_gid;
 116         }
 117     }
 118     return rc;
 119 }
 120 
 121 /*!
 122  * \internal
 123  * \brief Return the integer equivalent of a portion of a string
 124  *
 125  * \param[in]  text      Pointer to beginning of string portion
 126  * \param[out] end_text  This will point to next character after integer
 127  */
 128 static int
 129 version_helper(const char *text, const char **end_text)
     /* [previous][next][first][last][top][bottom][index][help] */
 130 {
 131     int atoi_result = -1;
 132 
 133     CRM_ASSERT(end_text != NULL);
 134 
 135     errno = 0;
 136 
 137     if (text != NULL && text[0] != 0) {
 138         /* seemingly sacrificing const-correctness -- because while strtol
 139            doesn't modify the input, it doesn't want to artificially taint the
 140            "end_text" pointer-to-pointer-to-first-char-in-string with constness
 141            in case the input wasn't actually constant -- by semantic definition
 142            not a single character will get modified so it shall be perfectly
 143            safe to make compiler happy with dropping "const" qualifier here */
 144         atoi_result = (int) strtol(text, (char **) end_text, 10);
 145 
 146         if (errno == EINVAL) {
 147             crm_err("Conversion of '%s' %c failed", text, text[0]);
 148             atoi_result = -1;
 149         }
 150     }
 151     return atoi_result;
 152 }
 153 
 154 /*
 155  * version1 < version2 : -1
 156  * version1 = version2 :  0
 157  * version1 > version2 :  1
 158  */
 159 int
 160 compare_version(const char *version1, const char *version2)
     /* [previous][next][first][last][top][bottom][index][help] */
 161 {
 162     int rc = 0;
 163     int lpc = 0;
 164     const char *ver1_iter, *ver2_iter;
 165 
 166     if (version1 == NULL && version2 == NULL) {
 167         return 0;
 168     } else if (version1 == NULL) {
 169         return -1;
 170     } else if (version2 == NULL) {
 171         return 1;
 172     }
 173 
 174     ver1_iter = version1;
 175     ver2_iter = version2;
 176 
 177     while (1) {
 178         int digit1 = 0;
 179         int digit2 = 0;
 180 
 181         lpc++;
 182 
 183         if (ver1_iter == ver2_iter) {
 184             break;
 185         }
 186 
 187         if (ver1_iter != NULL) {
 188             digit1 = version_helper(ver1_iter, &ver1_iter);
 189         }
 190 
 191         if (ver2_iter != NULL) {
 192             digit2 = version_helper(ver2_iter, &ver2_iter);
 193         }
 194 
 195         if (digit1 < digit2) {
 196             rc = -1;
 197             break;
 198 
 199         } else if (digit1 > digit2) {
 200             rc = 1;
 201             break;
 202         }
 203 
 204         if (ver1_iter != NULL && *ver1_iter == '.') {
 205             ver1_iter++;
 206         }
 207         if (ver1_iter != NULL && *ver1_iter == '\0') {
 208             ver1_iter = NULL;
 209         }
 210 
 211         if (ver2_iter != NULL && *ver2_iter == '.') {
 212             ver2_iter++;
 213         }
 214         if (ver2_iter != NULL && *ver2_iter == 0) {
 215             ver2_iter = NULL;
 216         }
 217     }
 218 
 219     if (rc == 0) {
 220         crm_trace("%s == %s (%d)", version1, version2, lpc);
 221     } else if (rc < 0) {
 222         crm_trace("%s < %s (%d)", version1, version2, lpc);
 223     } else if (rc > 0) {
 224         crm_trace("%s > %s (%d)", version1, version2, lpc);
 225     }
 226 
 227     return rc;
 228 }
 229 
 230 /*!
 231  * \brief Parse milliseconds from a Pacemaker interval specification
 232  *
 233  * \param[in] input  Pacemaker time interval specification (a bare number of
 234  *                   seconds, a number with a unit optionally with whitespace
 235  *                   before and/or after the number, or an ISO 8601 duration)
 236  *
 237  * \return Milliseconds equivalent of given specification on success (limited
 238  *         to the range of an unsigned integer), 0 if input is NULL,
 239  *         or 0 (and set errno to EINVAL) on error
 240  */
 241 guint
 242 crm_parse_interval_spec(const char *input)
     /* [previous][next][first][last][top][bottom][index][help] */
 243 {
 244     long long msec = -1;
 245 
 246     errno = 0;
 247     if (input == NULL) {
 248         return 0;
 249 
 250     } else if (input[0] == 'P') {
 251         crm_time_t *period_s = crm_time_parse_duration(input);
 252 
 253         if (period_s) {
 254             msec = 1000 * crm_time_get_seconds(period_s);
 255             crm_time_free(period_s);
 256         }
 257 
 258     } else {
 259         msec = crm_get_msec(input);
 260     }
 261 
 262     if (msec < 0) {
 263         crm_warn("Using 0 instead of '%s'", input);
 264         errno = EINVAL;
 265         return 0;
 266     }
 267     return (msec >= G_MAXUINT)? G_MAXUINT : (guint) msec;
 268 }
 269 
 270 /*!
 271  * \internal
 272  * \brief Log a failed assertion
 273  *
 274  * \param[in] file              File making the assertion
 275  * \param[in] function          Function making the assertion
 276  * \param[in] line              Line of file making the assertion
 277  * \param[in] assert_condition  String representation of assertion
 278  */
 279 static void
 280 log_assertion_as(const char *file, const char *function, int line,
     /* [previous][next][first][last][top][bottom][index][help] */
 281                  const char *assert_condition)
 282 {
 283     if (!pcmk__is_daemon) {
 284         crm_enable_stderr(TRUE); // Make sure command-line user sees message
 285     }
 286     crm_err("%s: Triggered fatal assertion at %s:%d : %s",
 287             function, file, line, assert_condition);
 288 }
 289 
 290 /* coverity[+kill] */
 291 /*!
 292  * \internal
 293  * \brief Log a failed assertion and abort
 294  *
 295  * \param[in] file              File making the assertion
 296  * \param[in] function          Function making the assertion
 297  * \param[in] line              Line of file making the assertion
 298  * \param[in] assert_condition  String representation of assertion
 299  *
 300  * \note This does not return
 301  */
 302 static _Noreturn void
 303 abort_as(const char *file, const char *function, int line,
     /* [previous][next][first][last][top][bottom][index][help] */
 304          const char *assert_condition)
 305 {
 306     log_assertion_as(file, function, line, assert_condition);
 307     abort();
 308 }
 309 
 310 /* coverity[+kill] */
 311 /*!
 312  * \internal
 313  * \brief Handle a failed assertion
 314  *
 315  * When called by a daemon, fork a child that aborts (to dump core), otherwise
 316  * abort the current process.
 317  *
 318  * \param[in] file              File making the assertion
 319  * \param[in] function          Function making the assertion
 320  * \param[in] line              Line of file making the assertion
 321  * \param[in] assert_condition  String representation of assertion
 322  */
 323 static void
 324 fail_assert_as(const char *file, const char *function, int line,
     /* [previous][next][first][last][top][bottom][index][help] */
 325                const char *assert_condition)
 326 {
 327     int status = 0;
 328     pid_t pid = 0;
 329 
 330     if (!pcmk__is_daemon) {
 331         abort_as(file, function, line, assert_condition); // does not return
 332     }
 333 
 334     pid = fork();
 335     switch (pid) {
 336         case -1: // Fork failed
 337             crm_warn("%s: Cannot dump core for non-fatal assertion at %s:%d "
 338                      ": %s", function, file, line, assert_condition);
 339             break;
 340 
 341         case 0: // Child process: just abort to dump core
 342             abort();
 343             break;
 344 
 345         default: // Parent process: wait for child
 346             crm_err("%s: Forked child [%d] to record non-fatal assertion at "
 347                     "%s:%d : %s", function, pid, file, line, assert_condition);
 348             crm_write_blackbox(SIGTRAP, NULL);
 349             do {
 350                 if (waitpid(pid, &status, 0) == pid) {
 351                     return; // Child finished dumping core
 352                 }
 353             } while (errno == EINTR);
 354             if (errno == ECHILD) {
 355                 // crm_mon ignores SIGCHLD
 356                 crm_trace("Cannot wait on forked child [%d] "
 357                           "(SIGCHLD is probably ignored)", pid);
 358             } else {
 359                 crm_err("Cannot wait on forked child [%d]: %s",
 360                         pid, pcmk_rc_str(errno));
 361             }
 362             break;
 363     }
 364 }
 365 
 366 /* coverity[+kill] */
 367 void
 368 crm_abort(const char *file, const char *function, int line,
     /* [previous][next][first][last][top][bottom][index][help] */
 369           const char *assert_condition, gboolean do_core, gboolean do_fork)
 370 {
 371     if (!do_fork) {
 372         abort_as(file, function, line, assert_condition);
 373     } else if (do_core) {
 374         fail_assert_as(file, function, line, assert_condition);
 375     } else {
 376         log_assertion_as(file, function, line, assert_condition);
 377     }
 378 }
 379 
 380 /*!
 381  * \internal
 382  * \brief Convert the current process to a daemon process
 383  *
 384  * Fork a child process, exit the parent, create a PID file with the current
 385  * process ID, and close the standard input/output/error file descriptors.
 386  * Exit instead if a daemon is already running and using the PID file.
 387  *
 388  * \param[in] name     Daemon executable name
 389  * \param[in] pidfile  File name to use as PID file
 390  */
 391 void
 392 pcmk__daemonize(const char *name, const char *pidfile)
     /* [previous][next][first][last][top][bottom][index][help] */
 393 {
 394     int rc;
 395     pid_t pid;
 396 
 397     /* Check before we even try... */
 398     rc = pcmk__pidfile_matches(pidfile, 1, name, &pid);
 399     if ((rc != pcmk_rc_ok) && (rc != ENOENT)) {
 400         crm_err("%s: already running [pid %lld in %s]",
 401                 name, (long long) pid, pidfile);
 402         printf("%s: already running [pid %lld in %s]\n",
 403                name, (long long) pid, pidfile);
 404         crm_exit(CRM_EX_ERROR);
 405     }
 406 
 407     pid = fork();
 408     if (pid < 0) {
 409         fprintf(stderr, "%s: could not start daemon\n", name);
 410         crm_perror(LOG_ERR, "fork");
 411         crm_exit(CRM_EX_OSERR);
 412 
 413     } else if (pid > 0) {
 414         crm_exit(CRM_EX_OK);
 415     }
 416 
 417     rc = pcmk__lock_pidfile(pidfile, name);
 418     if (rc != pcmk_rc_ok) {
 419         crm_err("Could not lock '%s' for %s: %s " CRM_XS " rc=%d",
 420                 pidfile, name, pcmk_rc_str(rc), rc);
 421         printf("Could not lock '%s' for %s: %s (%d)\n",
 422                pidfile, name, pcmk_rc_str(rc), rc);
 423         crm_exit(CRM_EX_ERROR);
 424     }
 425 
 426     umask(S_IWGRP | S_IWOTH | S_IROTH);
 427 
 428     close(STDIN_FILENO);
 429     pcmk__open_devnull(O_RDONLY);   // stdin (fd 0)
 430 
 431     close(STDOUT_FILENO);
 432     pcmk__open_devnull(O_WRONLY);   // stdout (fd 1)
 433 
 434     close(STDERR_FILENO);
 435     pcmk__open_devnull(O_WRONLY);   // stderr (fd 2)
 436 }
 437 
 438 char *
 439 crm_meta_name(const char *field)
     /* [previous][next][first][last][top][bottom][index][help] */
 440 {
 441     int lpc = 0;
 442     int max = 0;
 443     char *crm_name = NULL;
 444 
 445     CRM_CHECK(field != NULL, return NULL);
 446     crm_name = crm_strdup_printf(CRM_META "_%s", field);
 447 
 448     /* Massage the names so they can be used as shell variables */
 449     max = strlen(crm_name);
 450     for (; lpc < max; lpc++) {
 451         switch (crm_name[lpc]) {
 452             case '-':
 453                 crm_name[lpc] = '_';
 454                 break;
 455         }
 456     }
 457     return crm_name;
 458 }
 459 
 460 const char *
 461 crm_meta_value(GHashTable * hash, const char *field)
     /* [previous][next][first][last][top][bottom][index][help] */
 462 {
 463     char *key = NULL;
 464     const char *value = NULL;
 465 
 466     key = crm_meta_name(field);
 467     if (key) {
 468         value = g_hash_table_lookup(hash, key);
 469         free(key);
 470     }
 471 
 472     return value;
 473 }
 474 
 475 #ifdef HAVE_UUID_UUID_H
 476 #  include <uuid/uuid.h>
 477 #endif
 478 
 479 char *
 480 crm_generate_uuid(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 481 {
 482     unsigned char uuid[16];
 483     char *buffer = malloc(37);  /* Including NUL byte */
 484 
 485     uuid_generate(uuid);
 486     uuid_unparse(uuid, buffer);
 487     return buffer;
 488 }
 489 
 490 #ifdef HAVE_GNUTLS_GNUTLS_H
 491 void
 492 crm_gnutls_global_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 493 {
 494     signal(SIGPIPE, SIG_IGN);
 495     gnutls_global_init();
 496 }
 497 #endif
 498 
 499 /*!
 500  * \brief Get the local hostname
 501  *
 502  * \return Newly allocated string with name, or NULL (and set errno) on error
 503  */
 504 char *
 505 pcmk_hostname()
     /* [previous][next][first][last][top][bottom][index][help] */
 506 {
 507     struct utsname hostinfo;
 508 
 509     return (uname(&hostinfo) < 0)? NULL : strdup(hostinfo.nodename);
 510 }
 511 
 512 bool
 513 pcmk_str_is_infinity(const char *s) {
     /* [previous][next][first][last][top][bottom][index][help] */
 514     return pcmk__str_any_of(s, CRM_INFINITY_S, CRM_PLUS_INFINITY_S, NULL);
 515 }
 516 
 517 bool
 518 pcmk_str_is_minus_infinity(const char *s) {
     /* [previous][next][first][last][top][bottom][index][help] */
 519     return pcmk__str_eq(s, CRM_MINUS_INFINITY_S, pcmk__str_none);
 520 }
 521 
 522 /*!
 523  * \internal
 524  * \brief Sleep for given milliseconds
 525  *
 526  * \param[in] ms  Time to sleep
 527  *
 528  * \note The full time might not be slept if a signal is received.
 529  */
 530 void
 531 pcmk__sleep_ms(unsigned int ms)
     /* [previous][next][first][last][top][bottom][index][help] */
 532 {
 533     // @TODO Impose a sane maximum sleep to avoid hanging a process for long
 534     //CRM_CHECK(ms <= MAX_SLEEP, ms = MAX_SLEEP);
 535 
 536     // Use sleep() for any whole seconds
 537     if (ms >= 1000) {
 538         sleep(ms / 1000);
 539         ms -= ms / 1000;
 540     }
 541 
 542     if (ms == 0) {
 543         return;
 544     }
 545 
 546 #if defined(HAVE_NANOSLEEP)
 547     // nanosleep() is POSIX-2008, so prefer that
 548     {
 549         struct timespec req = { .tv_sec = 0, .tv_nsec = (long) (ms * 1000000) };
 550 
 551         nanosleep(&req, NULL);
 552     }
 553 #elif defined(HAVE_USLEEP)
 554     // usleep() is widely available, though considered obsolete
 555     usleep((useconds_t) ms);
 556 #else
 557     // Otherwise use a trick with select() timeout
 558     {
 559         struct timeval tv = { .tv_sec = 0, .tv_usec = (suseconds_t) ms };
 560 
 561         select(0, NULL, NULL, NULL, &tv);
 562     }
 563 #endif
 564 }

/* [previous][next][first][last][top][bottom][index][help] */