root/lib/common/utils.c

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

DEFINITIONS

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

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