root/lib/common/utils.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk_common_cleanup
  2. pcmk__is_user_in_group
  3. crm_user_lookup
  4. pcmk_daemon_user
  5. version_helper
  6. compare_version
  7. pcmk__daemonize
  8. crm_generate_uuid
  9. pcmk__sleep_ms
  10. pcmk__create_timer
  11. pcmk__timeout_ms2s
  12. _gnutls_log_func
  13. crm_gnutls_global_init
  14. crm_is_daemon_name

   1 /*
   2  * Copyright 2004-2025 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 #include <sys/stat.h>
  13 #include <sys/utsname.h>
  14 
  15 #include <stdio.h>
  16 #include <unistd.h>
  17 #include <string.h>
  18 #include <stdlib.h>
  19 #include <limits.h>
  20 #include <pwd.h>
  21 #include <time.h>
  22 #include <libgen.h>
  23 #include <signal.h>
  24 #include <grp.h>
  25 
  26 #include <qb/qbdefs.h>
  27 
  28 #include <crm/crm.h>
  29 #include <crm/services.h>
  30 #include <crm/cib/internal.h>
  31 #include <crm/common/xml.h>
  32 #include <crm/common/util.h>
  33 #include <crm/common/ipc.h>
  34 #include <crm/common/iso8601.h>
  35 #include <crm/common/mainloop.h>
  36 #include <libxml/parser.h>              // xmlCleanupParser()
  37 #include <libxml2/libxml/relaxng.h>
  38 
  39 #include "crmcommon_private.h"
  40 
  41 CRM_TRACE_INIT_DATA(common);
  42 
  43 bool pcmk__config_has_error = false;
  44 bool pcmk__config_has_warning = false;
  45 char *crm_system_name = NULL;
  46 
  47 /*!
  48  * \brief Free all memory used by libcrmcommon
  49  *
  50  * Free all global memory allocated by the libcrmcommon library. This should be
  51  * called before exiting a process that uses the library, and the process should
  52  * not call any libcrmcommon or libxml2 APIs after calling this one.
  53  */
  54 void
  55 pcmk_common_cleanup(void)
     /* [previous][next][first][last][top][bottom][index][help] */
  56 {
  57     // @TODO This isn't really everything, move all cleanup here
  58     mainloop_cleanup();
  59     pcmk__schema_cleanup();
  60     pcmk__free_common_logger();
  61 
  62     free(crm_system_name);
  63     crm_system_name = NULL;
  64 
  65     // Clean up external library global state
  66     qb_log_fini(); // Don't log anything after this point
  67     xmlCleanupParser();
  68 }
  69 
  70 bool
  71 pcmk__is_user_in_group(const char *user, const char *group)
     /* [previous][next][first][last][top][bottom][index][help] */
  72 {
  73     struct group *grent;
  74     char **gr_mem;
  75 
  76     if (user == NULL || group == NULL) {
  77         return false;
  78     }
  79     
  80     setgrent();
  81     while ((grent = getgrent()) != NULL) {
  82         if (grent->gr_mem == NULL) {
  83             continue;
  84         }
  85 
  86         if(strcmp(group, grent->gr_name) != 0) {
  87             continue;
  88         }
  89 
  90         gr_mem = grent->gr_mem;
  91         while (*gr_mem != NULL) {
  92             if (!strcmp(user, *gr_mem++)) {
  93                 endgrent();
  94                 return true;
  95             }
  96         }
  97     }
  98     endgrent();
  99     return false;
 100 }
 101 
 102 int
 103 crm_user_lookup(const char *name, uid_t * uid, gid_t * gid)
     /* [previous][next][first][last][top][bottom][index][help] */
 104 {
 105     int rc = pcmk_ok;
 106     char *buffer = NULL;
 107     struct passwd pwd;
 108     struct passwd *pwentry = NULL;
 109 
 110     buffer = calloc(1, PCMK__PW_BUFFER_LEN);
 111     if (buffer == NULL) {
 112         return -ENOMEM;
 113     }
 114 
 115     rc = getpwnam_r(name, &pwd, buffer, PCMK__PW_BUFFER_LEN, &pwentry);
 116     if (pwentry) {
 117         if (uid) {
 118             *uid = pwentry->pw_uid;
 119         }
 120         if (gid) {
 121             *gid = pwentry->pw_gid;
 122         }
 123         crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid);
 124 
 125     } else {
 126         rc = rc? -rc : -EINVAL;
 127         crm_info("User %s lookup: %s", name, pcmk_strerror(rc));
 128     }
 129 
 130     free(buffer);
 131     return rc;
 132 }
 133 
 134 /*!
 135  * \brief Get user and group IDs of pacemaker daemon user
 136  *
 137  * \param[out] uid  If non-NULL, where to store daemon user ID
 138  * \param[out] gid  If non-NULL, where to store daemon group ID
 139  *
 140  * \return pcmk_ok on success, -errno otherwise
 141  */
 142 int
 143 pcmk_daemon_user(uid_t *uid, gid_t *gid)
     /* [previous][next][first][last][top][bottom][index][help] */
 144 {
 145     static uid_t daemon_uid;
 146     static gid_t daemon_gid;
 147     static bool found = false;
 148     int rc = pcmk_ok;
 149 
 150     if (!found) {
 151         rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid);
 152         if (rc == pcmk_ok) {
 153             found = true;
 154         }
 155     }
 156     if (found) {
 157         if (uid) {
 158             *uid = daemon_uid;
 159         }
 160         if (gid) {
 161             *gid = daemon_gid;
 162         }
 163     }
 164     return rc;
 165 }
 166 
 167 /*!
 168  * \internal
 169  * \brief Return the integer equivalent of a portion of a string
 170  *
 171  * \param[in]  text      Pointer to beginning of string portion
 172  * \param[out] end_text  This will point to next character after integer
 173  */
 174 static int
 175 version_helper(const char *text, const char **end_text)
     /* [previous][next][first][last][top][bottom][index][help] */
 176 {
 177     int atoi_result = -1;
 178 
 179     pcmk__assert(end_text != NULL);
 180 
 181     errno = 0;
 182 
 183     if (text != NULL && text[0] != 0) {
 184         /* seemingly sacrificing const-correctness -- because while strtol
 185            doesn't modify the input, it doesn't want to artificially taint the
 186            "end_text" pointer-to-pointer-to-first-char-in-string with constness
 187            in case the input wasn't actually constant -- by semantic definition
 188            not a single character will get modified so it shall be perfectly
 189            safe to make compiler happy with dropping "const" qualifier here */
 190         atoi_result = (int) strtol(text, (char **) end_text, 10);
 191 
 192         if (errno == EINVAL) {
 193             crm_err("Conversion of '%s' %c failed", text, text[0]);
 194             atoi_result = -1;
 195         }
 196     }
 197     return atoi_result;
 198 }
 199 
 200 /*
 201  * version1 < version2 : -1
 202  * version1 = version2 :  0
 203  * version1 > version2 :  1
 204  */
 205 int
 206 compare_version(const char *version1, const char *version2)
     /* [previous][next][first][last][top][bottom][index][help] */
 207 {
 208     int rc = 0;
 209     int lpc = 0;
 210     const char *ver1_iter, *ver2_iter;
 211 
 212     if (version1 == NULL && version2 == NULL) {
 213         return 0;
 214     } else if (version1 == NULL) {
 215         return -1;
 216     } else if (version2 == NULL) {
 217         return 1;
 218     }
 219 
 220     ver1_iter = version1;
 221     ver2_iter = version2;
 222 
 223     while (1) {
 224         int digit1 = 0;
 225         int digit2 = 0;
 226 
 227         lpc++;
 228 
 229         if (ver1_iter == ver2_iter) {
 230             break;
 231         }
 232 
 233         if (ver1_iter != NULL) {
 234             digit1 = version_helper(ver1_iter, &ver1_iter);
 235         }
 236 
 237         if (ver2_iter != NULL) {
 238             digit2 = version_helper(ver2_iter, &ver2_iter);
 239         }
 240 
 241         if (digit1 < digit2) {
 242             rc = -1;
 243             break;
 244 
 245         } else if (digit1 > digit2) {
 246             rc = 1;
 247             break;
 248         }
 249 
 250         if (ver1_iter != NULL && *ver1_iter == '.') {
 251             ver1_iter++;
 252         }
 253         if (ver1_iter != NULL && *ver1_iter == '\0') {
 254             ver1_iter = NULL;
 255         }
 256 
 257         if (ver2_iter != NULL && *ver2_iter == '.') {
 258             ver2_iter++;
 259         }
 260         if (ver2_iter != NULL && *ver2_iter == 0) {
 261             ver2_iter = NULL;
 262         }
 263     }
 264 
 265     if (rc == 0) {
 266         crm_trace("%s == %s (%d)", version1, version2, lpc);
 267     } else if (rc < 0) {
 268         crm_trace("%s < %s (%d)", version1, version2, lpc);
 269     } else if (rc > 0) {
 270         crm_trace("%s > %s (%d)", version1, version2, lpc);
 271     }
 272 
 273     return rc;
 274 }
 275 
 276 /*!
 277  * \internal
 278  * \brief Convert the current process to a daemon process
 279  *
 280  * Fork a child process, exit the parent, create a PID file with the current
 281  * process ID, and close the standard input/output/error file descriptors.
 282  * Exit instead if a daemon is already running and using the PID file.
 283  *
 284  * \param[in] name     Daemon executable name
 285  * \param[in] pidfile  File name to use as PID file
 286  */
 287 void
 288 pcmk__daemonize(const char *name, const char *pidfile)
     /* [previous][next][first][last][top][bottom][index][help] */
 289 {
 290     int rc;
 291     pid_t pid;
 292 
 293     /* Check before we even try... */
 294     rc = pcmk__pidfile_matches(pidfile, 1, name, &pid);
 295     if ((rc != pcmk_rc_ok) && (rc != ENOENT)) {
 296         crm_err("%s: already running [pid %lld in %s]",
 297                 name, (long long) pid, pidfile);
 298         printf("%s: already running [pid %lld in %s]\n",
 299                name, (long long) pid, pidfile);
 300         crm_exit(CRM_EX_ERROR);
 301     }
 302 
 303     pid = fork();
 304     if (pid < 0) {
 305         fprintf(stderr, "%s: could not start daemon\n", name);
 306         crm_perror(LOG_ERR, "fork");
 307         crm_exit(CRM_EX_OSERR);
 308 
 309     } else if (pid > 0) {
 310         crm_exit(CRM_EX_OK);
 311     }
 312 
 313     rc = pcmk__lock_pidfile(pidfile, name);
 314     if (rc != pcmk_rc_ok) {
 315         crm_err("Could not lock '%s' for %s: %s " QB_XS " rc=%d",
 316                 pidfile, name, pcmk_rc_str(rc), rc);
 317         printf("Could not lock '%s' for %s: %s (%d)\n",
 318                pidfile, name, pcmk_rc_str(rc), rc);
 319         crm_exit(CRM_EX_ERROR);
 320     }
 321 
 322     umask(S_IWGRP | S_IWOTH | S_IROTH);
 323 
 324     close(STDIN_FILENO);
 325     pcmk__open_devnull(O_RDONLY);   // stdin (fd 0)
 326 
 327     close(STDOUT_FILENO);
 328     pcmk__open_devnull(O_WRONLY);   // stdout (fd 1)
 329 
 330     close(STDERR_FILENO);
 331     pcmk__open_devnull(O_WRONLY);   // stderr (fd 2)
 332 }
 333 
 334 #ifdef HAVE_UUID_UUID_H
 335 #  include <uuid/uuid.h>
 336 #endif
 337 
 338 char *
 339 crm_generate_uuid(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 340 {
 341     unsigned char uuid[16];
 342     char *buffer = malloc(37);  /* Including NUL byte */
 343 
 344     pcmk__mem_assert(buffer);
 345     uuid_generate(uuid);
 346     uuid_unparse(uuid, buffer);
 347     return buffer;
 348 }
 349 
 350 /*!
 351  * \internal
 352  * \brief Sleep for given milliseconds
 353  *
 354  * \param[in] ms  Time to sleep
 355  *
 356  * \note The full time might not be slept if a signal is received.
 357  */
 358 void
 359 pcmk__sleep_ms(unsigned int ms)
     /* [previous][next][first][last][top][bottom][index][help] */
 360 {
 361     // @TODO Impose a sane maximum sleep to avoid hanging a process for long
 362     //CRM_CHECK(ms <= MAX_SLEEP, ms = MAX_SLEEP);
 363 
 364     // Use sleep() for any whole seconds
 365     if (ms >= 1000) {
 366         sleep(ms / 1000);
 367         ms -= ms / 1000;
 368     }
 369 
 370     if (ms == 0) {
 371         return;
 372     }
 373 
 374 #if defined(HAVE_NANOSLEEP)
 375     // nanosleep() is POSIX-2008, so prefer that
 376     {
 377         struct timespec req = { .tv_sec = 0, .tv_nsec = (long) (ms * 1000000) };
 378 
 379         nanosleep(&req, NULL);
 380     }
 381 #elif defined(HAVE_USLEEP)
 382     // usleep() is widely available, though considered obsolete
 383     usleep((useconds_t) ms);
 384 #else
 385     // Otherwise use a trick with select() timeout
 386     {
 387         struct timeval tv = { .tv_sec = 0, .tv_usec = (suseconds_t) ms };
 388 
 389         select(0, NULL, NULL, NULL, &tv);
 390     }
 391 #endif
 392 }
 393 
 394 /*!
 395  * \internal
 396  * \brief Add a timer
 397  *
 398  * \param[in] interval_ms The interval for the function to be called, in ms
 399  * \param[in] fn          The function to be called
 400  * \param[in] data        Data to be passed to fn (can be NULL)
 401  *
 402  * \return The ID of the event source
 403  */
 404 guint
 405 pcmk__create_timer(guint interval_ms, GSourceFunc fn, gpointer data)
     /* [previous][next][first][last][top][bottom][index][help] */
 406 {
 407     pcmk__assert(interval_ms != 0 && fn != NULL);
 408 
 409     if (interval_ms % 1000 == 0) {
 410         /* In case interval_ms is 0, the call to pcmk__timeout_ms2s ensures
 411          * an interval of one second.
 412          */
 413         return g_timeout_add_seconds(pcmk__timeout_ms2s(interval_ms), fn, data);
 414     } else {
 415         return g_timeout_add(interval_ms, fn, data);
 416     }
 417 }
 418 
 419 /*!
 420  * \internal
 421  * \brief Convert milliseconds to seconds
 422  *
 423  * \param[in] timeout_ms The interval, in ms
 424  *
 425  * \return If \p timeout_ms is 0, return 0.  Otherwise, return the number of
 426  *         seconds, rounded to the nearest integer, with a minimum of 1.
 427  */
 428 guint
 429 pcmk__timeout_ms2s(guint timeout_ms)
     /* [previous][next][first][last][top][bottom][index][help] */
 430 {
 431     guint quot, rem;
 432 
 433     if (timeout_ms == 0) {
 434         return 0;
 435     } else if (timeout_ms < 1000) {
 436         return 1;
 437     }
 438 
 439     quot = timeout_ms / 1000;
 440     rem = timeout_ms % 1000;
 441 
 442     if (rem >= 500) {
 443         quot += 1;
 444     }
 445 
 446     return quot;
 447 }
 448 
 449 // Deprecated functions kept only for backward API compatibility
 450 // LCOV_EXCL_START
 451 
 452 #include <crm/common/util_compat.h>
 453 
 454 static void
 455 _gnutls_log_func(int level, const char *msg)
     /* [previous][next][first][last][top][bottom][index][help] */
 456 {
 457     crm_trace("%s", msg);
 458 }
 459 
 460 void
 461 crm_gnutls_global_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 462 {
 463     signal(SIGPIPE, SIG_IGN);
 464     gnutls_global_init();
 465     gnutls_global_set_log_level(8);
 466     gnutls_global_set_log_function(_gnutls_log_func);
 467 }
 468 
 469 /*!
 470  * \brief Check whether string represents a client name used by cluster daemons
 471  *
 472  * \param[in] name  String to check
 473  *
 474  * \return true if name is standard client name used by daemons, false otherwise
 475  *
 476  * \note This is provided by the client, and so cannot be used by itself as a
 477  *       secure means of authentication.
 478  */
 479 bool
 480 crm_is_daemon_name(const char *name)
     /* [previous][next][first][last][top][bottom][index][help] */
 481 {
 482     return pcmk__str_any_of(name,
 483                             "attrd",
 484                             CRM_SYSTEM_CIB,
 485                             CRM_SYSTEM_CRMD,
 486                             CRM_SYSTEM_DC,
 487                             CRM_SYSTEM_LRMD,
 488                             CRM_SYSTEM_MCP,
 489                             CRM_SYSTEM_PENGINE,
 490                             CRM_SYSTEM_TENGINE,
 491                             "pacemaker-attrd",
 492                             "pacemaker-based",
 493                             "pacemaker-controld",
 494                             "pacemaker-execd",
 495                             "pacemaker-fenced",
 496                             "pacemaker-remoted",
 497                             "pacemaker-schedulerd",
 498                             "stonith-ng",
 499                             "stonithd",
 500                             NULL);
 501 }
 502 
 503 // LCOV_EXCL_STOP
 504 // End deprecated API

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