diff --git a/include/crm/common/util.h b/include/crm/common/util.h index c75a55eb9d..24a93c92f6 100644 --- a/include/crm/common/util.h +++ b/include/crm/common/util.h @@ -1,131 +1,129 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_COMMON_UTIL__H # define PCMK__CRM_COMMON_UTIL__H # include // gid_t, mode_t, size_t, time_t, uid_t # include # include # include // uint32_t # include # include # include # include # include # include # include #ifdef __cplusplus extern "C" { #endif /** * \file * \brief Utility functions * \ingroup core */ # define ONLINESTATUS "online" // Status of an online client # define OFFLINESTATUS "offline" // Status of an offline client /* public node attribute functions (from attrd_client.c) */ char *pcmk_promotion_score_name(const char *rsc_id); /* public Pacemaker Remote functions (from remote.c) */ int crm_default_remote_port(void); /* public score-related functions (from scores.c) */ const char *pcmk_readable_score(int score); int char2score(const char *score); int pcmk__add_scores(int score1, int score2); /* public string functions (from strings.c) */ gboolean crm_is_true(const char *s); int crm_str_to_boolean(const char *s, int *ret); long long crm_get_msec(const char *input); char * crm_strip_trailing_newline(char *str); char *crm_strdup_printf(char const *format, ...) G_GNUC_PRINTF(1, 2); -guint crm_parse_interval_spec(const char *input); - int compare_version(const char *version1, const char *version2); /* coverity[+kill] */ void crm_abort(const char *file, const char *function, int line, const char *condition, gboolean do_core, gboolean do_fork); /*! * \brief Check whether any of specified flags are set in a flag group * * \param[in] flag_group The flag group being examined * \param[in] flags_to_check Which flags in flag_group should be checked * * \return true if \p flags_to_check is nonzero and any of its flags are set in * \p flag_group, or false otherwise */ static inline bool pcmk_any_flags_set(uint64_t flag_group, uint64_t flags_to_check) { return (flag_group & flags_to_check) != 0; } /*! * \brief Check whether all of specified flags are set in a flag group * * \param[in] flag_group The flag group being examined * \param[in] flags_to_check Which flags in flag_group should be checked * * \return true if \p flags_to_check is zero or all of its flags are set in * \p flag_group, or false otherwise */ static inline bool pcmk_all_flags_set(uint64_t flag_group, uint64_t flags_to_check) { return (flag_group & flags_to_check) == flags_to_check; } /*! * \brief Convenience alias for pcmk_all_flags_set(), to check single flag */ #define pcmk_is_set(g, f) pcmk_all_flags_set((g), (f)) char *crm_meta_name(const char *field); const char *crm_meta_value(GHashTable * hash, const char *field); char *crm_md5sum(const char *buffer); char *crm_generate_uuid(void); // This belongs in ipc.h but is here for backward compatibility bool crm_is_daemon_name(const char *name); int crm_user_lookup(const char *name, uid_t * uid, gid_t * gid); int pcmk_daemon_user(uid_t *uid, gid_t *gid); #ifdef HAVE_GNUTLS_GNUTLS_H void crm_gnutls_global_init(void); #endif char *pcmk_hostname(void); bool pcmk_str_is_infinity(const char *s); bool pcmk_str_is_minus_infinity(const char *s); #if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) #include #endif #ifdef __cplusplus } #endif #endif diff --git a/include/crm/common/util_compat.h b/include/crm/common/util_compat.h index 7a60208c30..8d988f516e 100644 --- a/include/crm/common/util_compat.h +++ b/include/crm/common/util_compat.h @@ -1,171 +1,174 @@ /* * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef PCMK__CRM_COMMON_UTIL_COMPAT__H # define PCMK__CRM_COMMON_UTIL_COMPAT__H # include # include # include #ifdef __cplusplus extern "C" { #endif /** * \file * \brief Deprecated Pacemaker utilities * \ingroup core * \deprecated Do not include this header directly. The utilities in this * header, and the header itself, will be removed in a future * release. */ -//! \deprecated Use crm_parse_interval_spec() instead +//! \deprecated Do not use #define crm_get_interval crm_parse_interval_spec //! \deprecated Do not use #define CRM_DEFAULT_OP_TIMEOUT_S "20s" //! \deprecated Use !pcmk_is_set() or !pcmk_all_flags_set() instead static inline gboolean is_not_set(long long word, long long bit) { return ((word & bit) == 0); } //! \deprecated Use pcmk_is_set() or pcmk_all_flags_set() instead static inline gboolean is_set(long long word, long long bit) { return ((word & bit) == bit); } //! \deprecated Use pcmk_any_flags_set() instead static inline gboolean is_set_any(long long word, long long bit) { return ((word & bit) != 0); } //! \deprecated Use strcmp() or strcasecmp() instead gboolean crm_str_eq(const char *a, const char *b, gboolean use_case); //! \deprecated Use strcmp() instead gboolean safe_str_neq(const char *a, const char *b); //! \deprecated Use strcasecmp() instead #define safe_str_eq(a, b) crm_str_eq(a, b, FALSE) //! \deprecated Use snprintf() instead char *crm_itoa_stack(int an_int, char *buf, size_t len); //! \deprecated Use sscanf() instead int pcmk_scan_nvpair(const char *input, char **name, char **value); //! \deprecated Use a standard printf()-style function instead char *pcmk_format_nvpair(const char *name, const char *value, const char *units); //! \deprecated Use \c crm_xml_add() or \c xml_remove_prop() instead const char *crm_xml_replace(xmlNode *node, const char *name, const char *value); //! \deprecated Use a standard printf()-style function instead char *pcmk_format_named_time(const char *name, time_t epoch_time); //! \deprecated Use strtoll() instead long long crm_parse_ll(const char *text, const char *default_text); //! \deprecated Use strtoll() instead int crm_parse_int(const char *text, const char *default_text); //! \deprecated Use strtoll() instead # define crm_atoi(text, default_text) crm_parse_int(text, default_text) //! \deprecated Use g_str_hash() instead guint g_str_hash_traditional(gconstpointer v); //! \deprecated Use g_str_hash() instead #define crm_str_hash g_str_hash_traditional //! \deprecated Do not use Pacemaker for generic string comparison gboolean crm_strcase_equal(gconstpointer a, gconstpointer b); //! \deprecated Do not use Pacemaker for generic string manipulation guint crm_strcase_hash(gconstpointer v); //! \deprecated Use g_hash_table_new_full() instead static inline GHashTable * crm_str_table_new(void) { return g_hash_table_new_full(crm_str_hash, g_str_equal, free, free); } //! \deprecated Use g_hash_table_new_full() instead static inline GHashTable * crm_strcase_table_new(void) { return g_hash_table_new_full(crm_strcase_hash, crm_strcase_equal, free, free); } //! \deprecated Do not use Pacemaker for generic hash table manipulation GHashTable *crm_str_table_dup(GHashTable *old_table); //! \deprecated Use g_hash_able_size() instead static inline guint crm_hash_table_size(GHashTable *hashtable) { if (hashtable == NULL) { return 0; } return g_hash_table_size(hashtable); } //! \deprecated Don't use Pacemaker for string manipulation char *crm_strip_trailing_newline(char *str); //! \deprecated Don't use Pacemaker for string manipulation int pcmk_numeric_strcasecmp(const char *s1, const char *s2); //! \deprecated Don't use Pacemaker for string manipulation static inline char * crm_itoa(int an_int) { return crm_strdup_printf("%d", an_int); } //! \deprecated Don't use Pacemaker for string manipulation static inline char * crm_ftoa(double a_float) { return crm_strdup_printf("%f", a_float); } //! \deprecated Don't use Pacemaker for string manipulation static inline char * crm_ttoa(time_t epoch_time) { return crm_strdup_printf("%lld", (long long) epoch_time); } //! \deprecated Do not use Pacemaker libraries for generic I/O void crm_build_path(const char *path_c, mode_t mode); //! \deprecated Use pcmk_readable_score() instead char *score2char(int score); //! \deprecated Use pcmk_readable_score() instead char *score2char_stack(int score, char *buf, size_t len); +//! \deprecated Do not use +guint crm_parse_interval_spec(const char *input); + #ifdef __cplusplus } #endif #endif // PCMK__CRM_COMMON_UTIL_COMPAT__H diff --git a/lib/common/utils.c b/lib/common/utils.c index e5b9ef0a19..fdc2b68bb8 100644 --- a/lib/common/utils.c +++ b/lib/common/utils.c @@ -1,594 +1,591 @@ /* - * Copyright 2004-2022 the Pacemaker project contributors + * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #include #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "crmcommon_private.h" CRM_TRACE_INIT_DATA(common); gboolean crm_config_error = FALSE; gboolean crm_config_warning = FALSE; char *crm_system_name = NULL; bool pcmk__is_user_in_group(const char *user, const char *group) { struct group *grent; char **gr_mem; if (user == NULL || group == NULL) { return false; } setgrent(); while ((grent = getgrent()) != NULL) { if (grent->gr_mem == NULL) { continue; } if(strcmp(group, grent->gr_name) != 0) { continue; } gr_mem = grent->gr_mem; while (*gr_mem != NULL) { if (!strcmp(user, *gr_mem++)) { endgrent(); return true; } } } endgrent(); return false; } int crm_user_lookup(const char *name, uid_t * uid, gid_t * gid) { int rc = pcmk_ok; char *buffer = NULL; struct passwd pwd; struct passwd *pwentry = NULL; buffer = calloc(1, PCMK__PW_BUFFER_LEN); if (buffer == NULL) { return -ENOMEM; } rc = getpwnam_r(name, &pwd, buffer, PCMK__PW_BUFFER_LEN, &pwentry); if (pwentry) { if (uid) { *uid = pwentry->pw_uid; } if (gid) { *gid = pwentry->pw_gid; } crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid); } else { rc = rc? -rc : -EINVAL; crm_info("User %s lookup: %s", name, pcmk_strerror(rc)); } free(buffer); return rc; } /*! * \brief Get user and group IDs of pacemaker daemon user * * \param[out] uid If non-NULL, where to store daemon user ID * \param[out] gid If non-NULL, where to store daemon group ID * * \return pcmk_ok on success, -errno otherwise */ int pcmk_daemon_user(uid_t *uid, gid_t *gid) { static uid_t daemon_uid; static gid_t daemon_gid; static bool found = false; int rc = pcmk_ok; if (!found) { rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid); if (rc == pcmk_ok) { found = true; } } if (found) { if (uid) { *uid = daemon_uid; } if (gid) { *gid = daemon_gid; } } return rc; } /*! * \internal * \brief Return the integer equivalent of a portion of a string * * \param[in] text Pointer to beginning of string portion * \param[out] end_text This will point to next character after integer */ static int version_helper(const char *text, const char **end_text) { int atoi_result = -1; CRM_ASSERT(end_text != NULL); errno = 0; if (text != NULL && text[0] != 0) { /* seemingly sacrificing const-correctness -- because while strtol doesn't modify the input, it doesn't want to artificially taint the "end_text" pointer-to-pointer-to-first-char-in-string with constness in case the input wasn't actually constant -- by semantic definition not a single character will get modified so it shall be perfectly safe to make compiler happy with dropping "const" qualifier here */ atoi_result = (int) strtol(text, (char **) end_text, 10); if (errno == EINVAL) { crm_err("Conversion of '%s' %c failed", text, text[0]); atoi_result = -1; } } return atoi_result; } /* * version1 < version2 : -1 * version1 = version2 : 0 * version1 > version2 : 1 */ int compare_version(const char *version1, const char *version2) { int rc = 0; int lpc = 0; const char *ver1_iter, *ver2_iter; if (version1 == NULL && version2 == NULL) { return 0; } else if (version1 == NULL) { return -1; } else if (version2 == NULL) { return 1; } ver1_iter = version1; ver2_iter = version2; while (1) { int digit1 = 0; int digit2 = 0; lpc++; if (ver1_iter == ver2_iter) { break; } if (ver1_iter != NULL) { digit1 = version_helper(ver1_iter, &ver1_iter); } if (ver2_iter != NULL) { digit2 = version_helper(ver2_iter, &ver2_iter); } if (digit1 < digit2) { rc = -1; break; } else if (digit1 > digit2) { rc = 1; break; } if (ver1_iter != NULL && *ver1_iter == '.') { ver1_iter++; } if (ver1_iter != NULL && *ver1_iter == '\0') { ver1_iter = NULL; } if (ver2_iter != NULL && *ver2_iter == '.') { ver2_iter++; } if (ver2_iter != NULL && *ver2_iter == 0) { ver2_iter = NULL; } } if (rc == 0) { crm_trace("%s == %s (%d)", version1, version2, lpc); } else if (rc < 0) { crm_trace("%s < %s (%d)", version1, version2, lpc); } else if (rc > 0) { crm_trace("%s > %s (%d)", version1, version2, lpc); } return rc; } -/*! - * \brief Parse milliseconds from a Pacemaker interval specification - * - * \param[in] input Pacemaker time interval specification (a bare number of - * seconds, a number with a unit optionally with whitespace - * before and/or after the number, or an ISO 8601 duration) - * - * \return Milliseconds equivalent of given specification on success (limited - * to the range of an unsigned integer), 0 if input is NULL, - * or 0 (and set errno to EINVAL) on error - */ -guint -crm_parse_interval_spec(const char *input) -{ - long long msec = -1; - - errno = 0; - if (input == NULL) { - return 0; - - } else if (input[0] == 'P') { - crm_time_t *period_s = crm_time_parse_duration(input); - - if (period_s) { - msec = 1000 * crm_time_get_seconds(period_s); - crm_time_free(period_s); - } - - } else { - msec = crm_get_msec(input); - } - - if (msec < 0) { - crm_warn("Using 0 instead of '%s'", input); - errno = EINVAL; - return 0; - } - return (msec >= G_MAXUINT)? G_MAXUINT : (guint) msec; -} - /*! * \internal * \brief Log a failed assertion * * \param[in] file File making the assertion * \param[in] function Function making the assertion * \param[in] line Line of file making the assertion * \param[in] assert_condition String representation of assertion */ static void log_assertion_as(const char *file, const char *function, int line, const char *assert_condition) { if (!pcmk__is_daemon) { crm_enable_stderr(TRUE); // Make sure command-line user sees message } crm_err("%s: Triggered fatal assertion at %s:%d : %s", function, file, line, assert_condition); } /* coverity[+kill] */ /*! * \internal * \brief Log a failed assertion and abort * * \param[in] file File making the assertion * \param[in] function Function making the assertion * \param[in] line Line of file making the assertion * \param[in] assert_condition String representation of assertion * * \note This does not return */ static _Noreturn void abort_as(const char *file, const char *function, int line, const char *assert_condition) { log_assertion_as(file, function, line, assert_condition); abort(); } /* coverity[+kill] */ /*! * \internal * \brief Handle a failed assertion * * When called by a daemon, fork a child that aborts (to dump core), otherwise * abort the current process. * * \param[in] file File making the assertion * \param[in] function Function making the assertion * \param[in] line Line of file making the assertion * \param[in] assert_condition String representation of assertion */ static void fail_assert_as(const char *file, const char *function, int line, const char *assert_condition) { int status = 0; pid_t pid = 0; if (!pcmk__is_daemon) { abort_as(file, function, line, assert_condition); // does not return } pid = fork(); switch (pid) { case -1: // Fork failed crm_warn("%s: Cannot dump core for non-fatal assertion at %s:%d " ": %s", function, file, line, assert_condition); break; case 0: // Child process: just abort to dump core abort(); break; default: // Parent process: wait for child crm_err("%s: Forked child [%d] to record non-fatal assertion at " "%s:%d : %s", function, pid, file, line, assert_condition); crm_write_blackbox(SIGTRAP, NULL); do { if (waitpid(pid, &status, 0) == pid) { return; // Child finished dumping core } } while (errno == EINTR); if (errno == ECHILD) { // crm_mon ignores SIGCHLD crm_trace("Cannot wait on forked child [%d] " "(SIGCHLD is probably ignored)", pid); } else { crm_err("Cannot wait on forked child [%d]: %s", pid, pcmk_rc_str(errno)); } break; } } /* coverity[+kill] */ void crm_abort(const char *file, const char *function, int line, const char *assert_condition, gboolean do_core, gboolean do_fork) { if (!do_fork) { abort_as(file, function, line, assert_condition); } else if (do_core) { fail_assert_as(file, function, line, assert_condition); } else { log_assertion_as(file, function, line, assert_condition); } } /*! * \internal * \brief Convert the current process to a daemon process * * Fork a child process, exit the parent, create a PID file with the current * process ID, and close the standard input/output/error file descriptors. * Exit instead if a daemon is already running and using the PID file. * * \param[in] name Daemon executable name * \param[in] pidfile File name to use as PID file */ void pcmk__daemonize(const char *name, const char *pidfile) { int rc; pid_t pid; /* Check before we even try... */ rc = pcmk__pidfile_matches(pidfile, 1, name, &pid); if ((rc != pcmk_rc_ok) && (rc != ENOENT)) { crm_err("%s: already running [pid %lld in %s]", name, (long long) pid, pidfile); printf("%s: already running [pid %lld in %s]\n", name, (long long) pid, pidfile); crm_exit(CRM_EX_ERROR); } pid = fork(); if (pid < 0) { fprintf(stderr, "%s: could not start daemon\n", name); crm_perror(LOG_ERR, "fork"); crm_exit(CRM_EX_OSERR); } else if (pid > 0) { crm_exit(CRM_EX_OK); } rc = pcmk__lock_pidfile(pidfile, name); if (rc != pcmk_rc_ok) { crm_err("Could not lock '%s' for %s: %s " CRM_XS " rc=%d", pidfile, name, pcmk_rc_str(rc), rc); printf("Could not lock '%s' for %s: %s (%d)\n", pidfile, name, pcmk_rc_str(rc), rc); crm_exit(CRM_EX_ERROR); } umask(S_IWGRP | S_IWOTH | S_IROTH); close(STDIN_FILENO); pcmk__open_devnull(O_RDONLY); // stdin (fd 0) close(STDOUT_FILENO); pcmk__open_devnull(O_WRONLY); // stdout (fd 1) close(STDERR_FILENO); pcmk__open_devnull(O_WRONLY); // stderr (fd 2) } char * crm_meta_name(const char *field) { int lpc = 0; int max = 0; char *crm_name = NULL; CRM_CHECK(field != NULL, return NULL); crm_name = crm_strdup_printf(CRM_META "_%s", field); /* Massage the names so they can be used as shell variables */ max = strlen(crm_name); for (; lpc < max; lpc++) { switch (crm_name[lpc]) { case '-': crm_name[lpc] = '_'; break; } } return crm_name; } const char * crm_meta_value(GHashTable * hash, const char *field) { char *key = NULL; const char *value = NULL; key = crm_meta_name(field); if (key) { value = g_hash_table_lookup(hash, key); free(key); } return value; } #ifdef HAVE_UUID_UUID_H # include #endif char * crm_generate_uuid(void) { unsigned char uuid[16]; char *buffer = malloc(37); /* Including NUL byte */ CRM_ASSERT(buffer != NULL); uuid_generate(uuid); uuid_unparse(uuid, buffer); return buffer; } #ifdef HAVE_GNUTLS_GNUTLS_H void crm_gnutls_global_init(void) { signal(SIGPIPE, SIG_IGN); gnutls_global_init(); } #endif /*! * \brief Get the local hostname * * \return Newly allocated string with name, or NULL (and set errno) on error */ char * pcmk_hostname(void) { struct utsname hostinfo; return (uname(&hostinfo) < 0)? NULL : strdup(hostinfo.nodename); } bool pcmk_str_is_infinity(const char *s) { return pcmk__str_any_of(s, CRM_INFINITY_S, CRM_PLUS_INFINITY_S, NULL); } bool pcmk_str_is_minus_infinity(const char *s) { return pcmk__str_eq(s, CRM_MINUS_INFINITY_S, pcmk__str_none); } /*! * \internal * \brief Sleep for given milliseconds * * \param[in] ms Time to sleep * * \note The full time might not be slept if a signal is received. */ void pcmk__sleep_ms(unsigned int ms) { // @TODO Impose a sane maximum sleep to avoid hanging a process for long //CRM_CHECK(ms <= MAX_SLEEP, ms = MAX_SLEEP); // Use sleep() for any whole seconds if (ms >= 1000) { sleep(ms / 1000); ms -= ms / 1000; } if (ms == 0) { return; } #if defined(HAVE_NANOSLEEP) // nanosleep() is POSIX-2008, so prefer that { struct timespec req = { .tv_sec = 0, .tv_nsec = (long) (ms * 1000000) }; nanosleep(&req, NULL); } #elif defined(HAVE_USLEEP) // usleep() is widely available, though considered obsolete usleep((useconds_t) ms); #else // Otherwise use a trick with select() timeout { struct timeval tv = { .tv_sec = 0, .tv_usec = (suseconds_t) ms }; select(0, NULL, NULL, NULL, &tv); } #endif } + +// Deprecated functions kept only for backward API compatibility +// LCOV_EXCL_START + +#include + +guint +crm_parse_interval_spec(const char *input) +{ + long long msec = -1; + + errno = 0; + if (input == NULL) { + return 0; + + } else if (input[0] == 'P') { + crm_time_t *period_s = crm_time_parse_duration(input); + + if (period_s) { + msec = 1000 * crm_time_get_seconds(period_s); + crm_time_free(period_s); + } + + } else { + msec = crm_get_msec(input); + } + + if (msec < 0) { + crm_warn("Using 0 instead of '%s'", input); + errno = EINVAL; + return 0; + } + return (msec >= G_MAXUINT)? G_MAXUINT : (guint) msec; +} + +// LCOV_EXCL_STOP +// End deprecated API