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. log_assertion_as
  7. abort_as
  8. fail_assert_as
  9. crm_abort
  10. pcmk__daemonize
  11. crm_generate_uuid
  12. crm_gnutls_global_init
  13. pcmk_str_is_infinity
  14. pcmk_str_is_minus_infinity
  15. pcmk__sleep_ms
  16. crm_parse_interval_spec
  17. pcmk_hostname

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

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