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

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