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. pcmk__daemonize
  7. crm_generate_uuid
  8. crm_gnutls_global_init
  9. pcmk_str_is_infinity
  10. pcmk_str_is_minus_infinity
  11. pcmk__sleep_ms
  12. crm_parse_interval_spec
  13. 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/stat.h>
  17 #include <sys/utsname.h>
  18 
  19 #include <stdio.h>
  20 #include <unistd.h>
  21 #include <string.h>
  22 #include <stdlib.h>
  23 #include <limits.h>
  24 #include <pwd.h>
  25 #include <time.h>
  26 #include <libgen.h>
  27 #include <signal.h>
  28 #include <grp.h>
  29 
  30 #include <qb/qbdefs.h>
  31 
  32 #include <crm/crm.h>
  33 #include <crm/services.h>
  34 #include <crm/cib/internal.h>
  35 #include <crm/common/xml.h>
  36 #include <crm/common/util.h>
  37 #include <crm/common/ipc.h>
  38 #include <crm/common/iso8601.h>
  39 #include <crm/common/mainloop.h>
  40 #include <libxml2/libxml/relaxng.h>
  41 
  42 #include "crmcommon_private.h"
  43 
  44 CRM_TRACE_INIT_DATA(common);
  45 
  46 gboolean crm_config_error = FALSE;
  47 gboolean crm_config_warning = FALSE;
  48 char *crm_system_name = NULL;
  49 
  50 bool
  51 pcmk__is_user_in_group(const char *user, const char *group)
     /* [previous][next][first][last][top][bottom][index][help] */
  52 {
  53     struct group *grent;
  54     char **gr_mem;
  55 
  56     if (user == NULL || group == NULL) {
  57         return false;
  58     }
  59     
  60     setgrent();
  61     while ((grent = getgrent()) != NULL) {
  62         if (grent->gr_mem == NULL) {
  63             continue;
  64         }
  65 
  66         if(strcmp(group, grent->gr_name) != 0) {
  67             continue;
  68         }
  69 
  70         gr_mem = grent->gr_mem;
  71         while (*gr_mem != NULL) {
  72             if (!strcmp(user, *gr_mem++)) {
  73                 endgrent();
  74                 return true;
  75             }
  76         }
  77     }
  78     endgrent();
  79     return false;
  80 }
  81 
  82 int
  83 crm_user_lookup(const char *name, uid_t * uid, gid_t * gid)
     /* [previous][next][first][last][top][bottom][index][help] */
  84 {
  85     int rc = pcmk_ok;
  86     char *buffer = NULL;
  87     struct passwd pwd;
  88     struct passwd *pwentry = NULL;
  89 
  90     buffer = calloc(1, PCMK__PW_BUFFER_LEN);
  91     if (buffer == NULL) {
  92         return -ENOMEM;
  93     }
  94 
  95     rc = getpwnam_r(name, &pwd, buffer, PCMK__PW_BUFFER_LEN, &pwentry);
  96     if (pwentry) {
  97         if (uid) {
  98             *uid = pwentry->pw_uid;
  99         }
 100         if (gid) {
 101             *gid = pwentry->pw_gid;
 102         }
 103         crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid);
 104 
 105     } else {
 106         rc = rc? -rc : -EINVAL;
 107         crm_info("User %s lookup: %s", name, pcmk_strerror(rc));
 108     }
 109 
 110     free(buffer);
 111     return rc;
 112 }
 113 
 114 /*!
 115  * \brief Get user and group IDs of pacemaker daemon user
 116  *
 117  * \param[out] uid  If non-NULL, where to store daemon user ID
 118  * \param[out] gid  If non-NULL, where to store daemon group ID
 119  *
 120  * \return pcmk_ok on success, -errno otherwise
 121  */
 122 int
 123 pcmk_daemon_user(uid_t *uid, gid_t *gid)
     /* [previous][next][first][last][top][bottom][index][help] */
 124 {
 125     static uid_t daemon_uid;
 126     static gid_t daemon_gid;
 127     static bool found = false;
 128     int rc = pcmk_ok;
 129 
 130     if (!found) {
 131         rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid);
 132         if (rc == pcmk_ok) {
 133             found = true;
 134         }
 135     }
 136     if (found) {
 137         if (uid) {
 138             *uid = daemon_uid;
 139         }
 140         if (gid) {
 141             *gid = daemon_gid;
 142         }
 143     }
 144     return rc;
 145 }
 146 
 147 /*!
 148  * \internal
 149  * \brief Return the integer equivalent of a portion of a string
 150  *
 151  * \param[in]  text      Pointer to beginning of string portion
 152  * \param[out] end_text  This will point to next character after integer
 153  */
 154 static int
 155 version_helper(const char *text, const char **end_text)
     /* [previous][next][first][last][top][bottom][index][help] */
 156 {
 157     int atoi_result = -1;
 158 
 159     pcmk__assert(end_text != NULL);
 160 
 161     errno = 0;
 162 
 163     if (text != NULL && text[0] != 0) {
 164         /* seemingly sacrificing const-correctness -- because while strtol
 165            doesn't modify the input, it doesn't want to artificially taint the
 166            "end_text" pointer-to-pointer-to-first-char-in-string with constness
 167            in case the input wasn't actually constant -- by semantic definition
 168            not a single character will get modified so it shall be perfectly
 169            safe to make compiler happy with dropping "const" qualifier here */
 170         atoi_result = (int) strtol(text, (char **) end_text, 10);
 171 
 172         if (errno == EINVAL) {
 173             crm_err("Conversion of '%s' %c failed", text, text[0]);
 174             atoi_result = -1;
 175         }
 176     }
 177     return atoi_result;
 178 }
 179 
 180 /*
 181  * version1 < version2 : -1
 182  * version1 = version2 :  0
 183  * version1 > version2 :  1
 184  */
 185 int
 186 compare_version(const char *version1, const char *version2)
     /* [previous][next][first][last][top][bottom][index][help] */
 187 {
 188     int rc = 0;
 189     int lpc = 0;
 190     const char *ver1_iter, *ver2_iter;
 191 
 192     if (version1 == NULL && version2 == NULL) {
 193         return 0;
 194     } else if (version1 == NULL) {
 195         return -1;
 196     } else if (version2 == NULL) {
 197         return 1;
 198     }
 199 
 200     ver1_iter = version1;
 201     ver2_iter = version2;
 202 
 203     while (1) {
 204         int digit1 = 0;
 205         int digit2 = 0;
 206 
 207         lpc++;
 208 
 209         if (ver1_iter == ver2_iter) {
 210             break;
 211         }
 212 
 213         if (ver1_iter != NULL) {
 214             digit1 = version_helper(ver1_iter, &ver1_iter);
 215         }
 216 
 217         if (ver2_iter != NULL) {
 218             digit2 = version_helper(ver2_iter, &ver2_iter);
 219         }
 220 
 221         if (digit1 < digit2) {
 222             rc = -1;
 223             break;
 224 
 225         } else if (digit1 > digit2) {
 226             rc = 1;
 227             break;
 228         }
 229 
 230         if (ver1_iter != NULL && *ver1_iter == '.') {
 231             ver1_iter++;
 232         }
 233         if (ver1_iter != NULL && *ver1_iter == '\0') {
 234             ver1_iter = NULL;
 235         }
 236 
 237         if (ver2_iter != NULL && *ver2_iter == '.') {
 238             ver2_iter++;
 239         }
 240         if (ver2_iter != NULL && *ver2_iter == 0) {
 241             ver2_iter = NULL;
 242         }
 243     }
 244 
 245     if (rc == 0) {
 246         crm_trace("%s == %s (%d)", version1, version2, lpc);
 247     } else 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     }
 252 
 253     return rc;
 254 }
 255 
 256 /*!
 257  * \internal
 258  * \brief Convert the current process to a daemon process
 259  *
 260  * Fork a child process, exit the parent, create a PID file with the current
 261  * process ID, and close the standard input/output/error file descriptors.
 262  * Exit instead if a daemon is already running and using the PID file.
 263  *
 264  * \param[in] name     Daemon executable name
 265  * \param[in] pidfile  File name to use as PID file
 266  */
 267 void
 268 pcmk__daemonize(const char *name, const char *pidfile)
     /* [previous][next][first][last][top][bottom][index][help] */
 269 {
 270     int rc;
 271     pid_t pid;
 272 
 273     /* Check before we even try... */
 274     rc = pcmk__pidfile_matches(pidfile, 1, name, &pid);
 275     if ((rc != pcmk_rc_ok) && (rc != ENOENT)) {
 276         crm_err("%s: already running [pid %lld in %s]",
 277                 name, (long long) pid, pidfile);
 278         printf("%s: already running [pid %lld in %s]\n",
 279                name, (long long) pid, pidfile);
 280         crm_exit(CRM_EX_ERROR);
 281     }
 282 
 283     pid = fork();
 284     if (pid < 0) {
 285         fprintf(stderr, "%s: could not start daemon\n", name);
 286         crm_perror(LOG_ERR, "fork");
 287         crm_exit(CRM_EX_OSERR);
 288 
 289     } else if (pid > 0) {
 290         crm_exit(CRM_EX_OK);
 291     }
 292 
 293     rc = pcmk__lock_pidfile(pidfile, name);
 294     if (rc != pcmk_rc_ok) {
 295         crm_err("Could not lock '%s' for %s: %s " CRM_XS " rc=%d",
 296                 pidfile, name, pcmk_rc_str(rc), rc);
 297         printf("Could not lock '%s' for %s: %s (%d)\n",
 298                pidfile, name, pcmk_rc_str(rc), rc);
 299         crm_exit(CRM_EX_ERROR);
 300     }
 301 
 302     umask(S_IWGRP | S_IWOTH | S_IROTH);
 303 
 304     close(STDIN_FILENO);
 305     pcmk__open_devnull(O_RDONLY);   // stdin (fd 0)
 306 
 307     close(STDOUT_FILENO);
 308     pcmk__open_devnull(O_WRONLY);   // stdout (fd 1)
 309 
 310     close(STDERR_FILENO);
 311     pcmk__open_devnull(O_WRONLY);   // stderr (fd 2)
 312 }
 313 
 314 #ifdef HAVE_UUID_UUID_H
 315 #  include <uuid/uuid.h>
 316 #endif
 317 
 318 char *
 319 crm_generate_uuid(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 320 {
 321     unsigned char uuid[16];
 322     char *buffer = malloc(37);  /* Including NUL byte */
 323 
 324     pcmk__mem_assert(buffer);
 325     uuid_generate(uuid);
 326     uuid_unparse(uuid, buffer);
 327     return buffer;
 328 }
 329 
 330 #ifdef HAVE_GNUTLS_GNUTLS_H
 331 void
 332 crm_gnutls_global_init(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 333 {
 334     signal(SIGPIPE, SIG_IGN);
 335     gnutls_global_init();
 336 }
 337 #endif
 338 
 339 bool
 340 pcmk_str_is_infinity(const char *s) {
     /* [previous][next][first][last][top][bottom][index][help] */
 341     return pcmk__str_any_of(s, PCMK_VALUE_INFINITY, PCMK_VALUE_PLUS_INFINITY,
 342                             NULL);
 343 }
 344 
 345 bool
 346 pcmk_str_is_minus_infinity(const char *s) {
     /* [previous][next][first][last][top][bottom][index][help] */
 347     return pcmk__str_eq(s, PCMK_VALUE_MINUS_INFINITY, pcmk__str_none);
 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 // Deprecated functions kept only for backward API compatibility
 395 // LCOV_EXCL_START
 396 
 397 #include <crm/common/util_compat.h>
 398 
 399 guint
 400 crm_parse_interval_spec(const char *input)
     /* [previous][next][first][last][top][bottom][index][help] */
 401 {
 402     long long msec = -1;
 403 
 404     errno = 0;
 405     if (input == NULL) {
 406         return 0;
 407 
 408     } else if (input[0] == 'P') {
 409         crm_time_t *period_s = crm_time_parse_duration(input);
 410 
 411         if (period_s) {
 412             msec = 1000 * crm_time_get_seconds(period_s);
 413             crm_time_free(period_s);
 414         }
 415 
 416     } else {
 417         msec = crm_get_msec(input);
 418     }
 419 
 420     if (msec < 0) {
 421         crm_warn("Using 0 instead of '%s'", input);
 422         errno = EINVAL;
 423         return 0;
 424     }
 425     return (msec >= G_MAXUINT)? G_MAXUINT : (guint) msec;
 426 }
 427 
 428 char *
 429 pcmk_hostname(void)
     /* [previous][next][first][last][top][bottom][index][help] */
 430 {
 431     struct utsname hostinfo;
 432 
 433     return (uname(&hostinfo) < 0)? NULL : strdup(hostinfo.nodename);
 434 }
 435 
 436 // LCOV_EXCL_STOP
 437 // End deprecated API

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