diff --git a/lib/common/tests/utils/pcmk__compare_versions_test.c b/lib/common/tests/utils/pcmk__compare_versions_test.c index f9da064791..4ea0fe2665 100644 --- a/lib/common/tests/utils/pcmk__compare_versions_test.c +++ b/lib/common/tests/utils/pcmk__compare_versions_test.c @@ -1,318 +1,318 @@ /* * Copyright 2022-2025 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU General Public License version 2 * or later (GPLv2+) WITHOUT ANY WARRANTY. */ #include #include /*! * \internal * \brief Compare two version strings in both directions * * \param[in] v1 First argument for \c pcmk__compare_versions() * \param[in] v2 Second argument for \c pcmk__compare_versions() * \param[in] expected_rc Expected return code from * pcmk__compare_versions(v1, v2) */ static void assert_compare_version(const char *v1, const char *v2, int expected_rc) { assert_int_equal(pcmk__compare_versions(v1, v2), expected_rc); if (v1 != v2) { /* Try reverse order even if expected_rc == 0, if v1 and v2 are * different strings */ assert_int_equal(pcmk__compare_versions(v2, v1), -expected_rc); } } static void empty_params(void **state) { assert_compare_version(NULL, NULL, 0); assert_compare_version(NULL, "", 0); assert_compare_version("", "", 0); assert_compare_version(NULL, "1.0.1", -1); assert_compare_version("", "1.0.1", -1); - // @FIXME NULL/empty should be equal to an invalid version - assert_compare_version(NULL, "abc", -1); // Should be 0 - assert_compare_version("", "abc", -1); // Should be 0 + // NULL or empty is treated as equal to an invalid version + assert_compare_version(NULL, "abc", 0); + assert_compare_version("", "abc", 0); } static void equal_versions(void **state) { assert_compare_version("0.4.7", "0.4.7", 0); assert_compare_version("1.0", "1.0", 0); } static void unequal_versions(void **state) { assert_compare_version("0.4.7", "0.4.8", -1); assert_compare_version("0.2.3", "0.3", -1); assert_compare_version("0.99", "1.0", -1); } static void shorter_versions(void **state) { assert_compare_version("1.0", "1.0.1", -1); assert_compare_version("1.0", "1", 0); assert_compare_version("1", "1.2", -1); assert_compare_version("1.0.0", "1.0", 0); assert_compare_version("1.0.0", "1.2", -1); assert_compare_version("0.99", "1", -1); } static void leading_zeros(void **state) { // Equal to self assert_compare_version("00001.0", "00001.0", 0); // Leading zeros in each segment are ignored assert_compare_version("0001.0", "1", 0); assert_compare_version("0.0001", "0.1", 0); assert_compare_version("0001.1", "1.0001", 0); } static void negative_sign(void **state) { // Equal to self assert_compare_version("-1", "-1", 0); assert_compare_version("1.-1.5", "1.-1.5", 0); // Negative version is treated as 0 (invalid) assert_compare_version("-1", "0", 0); assert_compare_version("-1", "0.0", 0); assert_compare_version("-1", "0.1", -1); assert_compare_version("-1", "1.0", -1); assert_compare_version("-1", "-0", 0); assert_compare_version("-1", "-0.0", 0); assert_compare_version("-1", "-0.1", 0); assert_compare_version("-1", "-1.0", 0); assert_compare_version("-1", "-2.0", 0); // Negative sign inside version is treated as garbage assert_compare_version("1.-1.5", "1.0", 0); assert_compare_version("1.-1.5", "1.0.5", -1); assert_compare_version("1.-1.5", "1.-0", 0); assert_compare_version("1.-1.5", "1.-0.5", 0); assert_compare_version("1.-1.5", "1.-1", 0); assert_compare_version("1.-1.5", "1.-1.9", 0); assert_compare_version("1.-1.5", "1.-2", 0); assert_compare_version("1.-1.5", "1.-2.5", 0); assert_compare_version("1.-1.5", "2.0.5", -1); assert_compare_version("1.-1.5", "0.0.5", 1); } static void positive_sign(void **state) { // Equal to self assert_compare_version("+1", "+1", 0); assert_compare_version("1.+1.5", "1.+1.5", 0); // @FIXME Treat version with explicit positive sign as 0 (invalid) assert_compare_version("+1", "0", 0); assert_compare_version("+1", "0.0", 0); assert_compare_version("+1", "0.1", -1); assert_compare_version("+1", "1.0", -1); assert_compare_version("+1", "2.0", -1); assert_compare_version("+1", "+0", 0); assert_compare_version("+1", "+0.0", 0); assert_compare_version("+1", "+0.1", 0); assert_compare_version("+1", "+1.0", 0); assert_compare_version("+1", "+2.0", 0); // @FIXME Treat positive sign inside version as garbage assert_compare_version("1.+1.5", "1.0", 0); assert_compare_version("1.+1.5", "1.0.5", -1); assert_compare_version("1.+1.5", "1.+0", 0); assert_compare_version("1.+1.5", "1.+0.5", 0); assert_compare_version("1.+1.5", "1.+1", 0); assert_compare_version("1.+1.5", "1.+1.9", 0); assert_compare_version("1.+1.5", "1.+2", 0); assert_compare_version("1.+1.5", "1.+2.5", 0); assert_compare_version("1.+1.5", "2.0.5", -1); assert_compare_version("1.+1.5", "0.0.5", 1); } static void hex_digits(void **state) { // Equal to self assert_compare_version("a", "a", 0); // Hex digits > 9 are garbage assert_compare_version("a", "0", 0); assert_compare_version("a111", "0", 0); assert_compare_version("a", "1", -1); assert_compare_version("a111", "1", -1); assert_compare_version("1a", "1", 0); assert_compare_version("1a111", "1", 0); assert_compare_version("1a", "2", -1); assert_compare_version("1a111", "2", -1); assert_compare_version("1a", "0", 1); assert_compare_version("1a111", "0", 1); } static void bare_dot(void **state) { // Equal to self assert_compare_version(".", ".", 0); // Bare dot is treated as 0 assert_compare_version(".", "0", 0); assert_compare_version(".", "0.1", -1); assert_compare_version(".", "1.0", -1); } static void leading_dot(void **state) { // Equal to self assert_compare_version(".0", ".0", 0); assert_compare_version(".1", ".1", 0); // Version with leading dot is treated as 0 assert_compare_version(".0", "0", 0); assert_compare_version(".0", "0.0", 0); assert_compare_version(".0", "0.0.0", 0); assert_compare_version(".0", "0.1", -1); assert_compare_version(".1", "0", 0); assert_compare_version(".1", "0.0", 0); assert_compare_version(".1", "0.0.0", 0); assert_compare_version(".1", "0.1", -1); assert_compare_version(".1", "0.1.0", -1); } static void trailing_dot(void **state) { // Equal to self assert_compare_version("0.", "0.", 0); assert_compare_version("0.1.", "0.1.", 0); // Trailing dot is ignored assert_compare_version("0.", "0", 0); assert_compare_version("0.", "0.0", 0); assert_compare_version("0.", "0.1", -1); assert_compare_version("0.1.", "0.1", 0); assert_compare_version("0.1.", "0.1.0", 0); assert_compare_version("0.1.", "0.2", -1); assert_compare_version("0.1.", "0", 1); } static void leading_spaces(void **state) { // Equal to self assert_compare_version(" ", " ", 0); assert_compare_version(" 1", " 1", 0); // Leading spaces are ignored assert_compare_version(" 1", "1.0", 0); assert_compare_version("1", " 1.0", 0); assert_compare_version(" 1", " 1.0", 0); assert_compare_version(" 1", "1.1", -1); assert_compare_version("1", " 1.1", -1); assert_compare_version(" 1", " 1.1", -1); } static void trailing_spaces(void **state) { // Equal to self assert_compare_version("1 ", "1 ", 0); // Trailing spaces are ignored assert_compare_version("1 ", "1.0", 0); assert_compare_version("1", "1.0 ", 0); assert_compare_version("1 ", "1.0 ", 0); assert_compare_version("1 ", "1.1", -1); assert_compare_version("1", "1.1 ", -1); assert_compare_version("1 ", "1.1 ", -1); } static void leading_garbage(void **state) { // Equal to self assert_compare_version("@1", "@1", 0); // Version with leading garbage is treated as 0 assert_compare_version("@1", "0", 0); assert_compare_version("@1", "1", -1); assert_compare_version("@0.1", "0", 0); assert_compare_version("@0.1", "1", -1); } static void trailing_garbage(void **state) { // Equal to self assert_compare_version("0.1@", "0.1@", 0); // Trailing garbage is ignored assert_compare_version("0.1@", "0.1", 0); assert_compare_version("0.1.@", "0.1", 0); assert_compare_version("0.1 @", "0.1", 0); assert_compare_version("0.1. @", "0.1", 0); assert_compare_version("0.1 .@", "0.1", 0); // This includes more numbers after spaces assert_compare_version("0.1 1", "0.1", 0); assert_compare_version("0.1. 1", "0.1", 0); assert_compare_version("0.1 .1", "0.1", 0); // Second consecutive dot is treated as garbage assert_compare_version("1..", "1", 0); assert_compare_version("1..1", "1", 0); assert_compare_version("1..", "1.0.0", 0); assert_compare_version("1..1", "1.0.0", 0); assert_compare_version("1..", "1.0.1", -1); assert_compare_version("1..1", "1.0.1", -1); } PCMK__UNIT_TEST(NULL, NULL, cmocka_unit_test(empty_params), cmocka_unit_test(equal_versions), cmocka_unit_test(unequal_versions), cmocka_unit_test(shorter_versions), cmocka_unit_test(leading_zeros), cmocka_unit_test(negative_sign), cmocka_unit_test(positive_sign), cmocka_unit_test(hex_digits), cmocka_unit_test(bare_dot), cmocka_unit_test(leading_dot), cmocka_unit_test(trailing_dot), cmocka_unit_test(leading_spaces), cmocka_unit_test(trailing_spaces), cmocka_unit_test(leading_garbage), cmocka_unit_test(trailing_garbage)) diff --git a/lib/common/utils.c b/lib/common/utils.c index acf3d8d603..1e50cf33f9 100644 --- a/lib/common/utils.c +++ b/lib/common/utils.c @@ -1,710 +1,695 @@ /* * Copyright 2004-2025 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 #include #include #include #include #include #include #include #include #include #include +#include // regex_t, etc. #include #include #include #include #include #include #include #include #include #include #include #include #include "crmcommon_private.h" CRM_TRACE_INIT_DATA(common); bool pcmk__config_has_error = false; bool pcmk__config_has_warning = false; char *crm_system_name = NULL; /*! * \brief Free all memory used by libcrmcommon * * Free all global memory allocated by the libcrmcommon library. This should be * called before exiting a process that uses the library, and the process should * not call any libcrmcommon or libxml2 APIs after calling this one. */ void pcmk_common_cleanup(void) { // @TODO This isn't really everything, move all cleanup here mainloop_cleanup(); pcmk__xml_cleanup(); pcmk__free_common_logger(); qb_log_fini(); // Don't log anything after this point free(crm_system_name); 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 pcmk__lookup_user(const char *name, uid_t *uid, gid_t *gid) { struct passwd *pwentry = NULL; CRM_CHECK(name != NULL, return EINVAL); // getpwnam() is not thread-safe, but Pacemaker is single-threaded errno = 0; pwentry = getpwnam(name); if (pwentry == NULL) { /* Either an error occurred or no passwd entry was found. * * The value of errno is implementation-dependent if no passwd entry is * found. The POSIX specification does not consider it an error. * POSIX.1-2008 specifies that errno shall not be changed in this case, * while POSIX.1-2001 does not specify the value of errno in this case. * The man page on Linux notes that a variety of values have been * observed in practice. So an implementation may set errno to an * arbitrary value, despite the POSIX specification. * * However, if pwentry == NULL and errno == 0, then we know that no * matching entry was found and there was no error. So we default to * ENOENT as our return code. */ return ((errno != 0)? errno : ENOENT); } if (uid != NULL) { *uid = pwentry->pw_uid; } if (gid != NULL) { *gid = pwentry->pw_gid; } crm_trace("User %s has uid=%lld gid=%lld", name, (long long) pwentry->pw_uid, (long long) pwentry->pw_gid); return pcmk_rc_ok; } /*! * \internal * \brief Get user and group IDs of Pacemaker daemon user * * \param[out] uid Where to store daemon user ID (can be \c NULL) * \param[out] gid Where to store daemon group ID (can be \c NULL) * * \return Standard Pacemaker return code */ int pcmk__daemon_user(uid_t *uid, gid_t *gid) { static uid_t daemon_uid = 0; static gid_t daemon_gid = 0; static bool found = false; if (!found) { int rc = pcmk__lookup_user(CRM_DAEMON_USER, &daemon_uid, &daemon_gid); if (rc != pcmk_rc_ok) { return rc; } found = true; } if (uid != NULL) { *uid = daemon_uid; } if (gid != NULL) { *gid = daemon_gid; } return pcmk_rc_ok; } /*! * \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; pcmk__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; } -/*! - * \internal - * \brief Parse a version segment from an input and advance the input pointer - * - * \param[in,out] version_segment Pointer to a version string segment - * - * \return Nonnegative integer value parsed from \p *version_segment on success, - * or 0 on failure - * - * \note Upon return, \p *version_segment points to the (possibly invalid) next - * segment on success, or to the terminating null byte on failure. - */ -static long -parse_version_segment(const char **version_segment) -{ - char *endptr = NULL; - long rc = 0; - - if (pcmk__str_empty(*version_segment)) { - return 0; - } - - /* '+', '-', and whitespace are invalid in version strings. strtol() won't - * complain, so catch them here. If the first character is not a digit, - * advance to end of string. - */ - if (!isdigit(**version_segment)) { - *version_segment += strlen(*version_segment); - return 0; - } - - /* Negative return code or unparsable input should be impossible, since we - * checked with isdigit() first. If it happens somehow, advance to end of - * string. - */ - rc = strtol(*version_segment, &endptr, 10); - CRM_CHECK((rc >= 0) && (endptr != *version_segment), - *version_segment = endptr + strlen(endptr); return 0); - - // Skip one dot immediately after a series of digits - if (*endptr == '.') { - endptr++; - } - - // Advance to next version segment - *version_segment = endptr; - return rc; -} - /*! * \internal * \brief Compare two version strings to determine which one is higher * * A valid version string is of the form specified by the regex * [0-9]+(\.[0-9]+)*. * - * Leading whitespace is allowed and ignored. The two strings are compared - * segment by segment, until either the terminating null byte or an invalid - * character has been reached in both strings. A segment is a series of digits - * followed by a single dot or by the terminating null byte. + * Leading whitespace and trailing garbage are allowed and ignored. * - * After the terminating null byte or an invalid character is reached in one - * string, parsing of that string stops. All further comparisons are as if that - * string has an infinite number of trailing \c "0." segments. This continues - * until the terminating null byte or an invalid character is reached in the - * other string. + * For each string, we get all segments until the first invalid character. A + * segment is a series of digits, and segments are delimited by a single dot. + * The two strings are compared segment by segment, until either we find a + * difference or we've processed all segments in both strings. * - * Segments are compared by calling \c strtol() to parse them to long integers, - * and then performing standard integer comparison. + * If one string runs out of segments to compare before the other string does, + * we treat it as if it has enough padding \c "0" segments to finish the + * comparisons. + * + * Segments are compared by calling \c strtoll() to parse them to long long + * integers and then performing standard integer comparison. * * \param[in] version1 First version to compare * \param[in] version2 Second version to compare * * \retval -1 if \p version1 evaluates to a lower version than \p version2 * \retval 1 if \p version1 evaluates to a higher version than \p version2 * \retval 0 if \p version1 and \p version2 evaluate to an equal version + * + * \note Each version segment's parsed value must fit into a long long. */ int pcmk__compare_versions(const char *version1, const char *version2) { + regex_t regex; + regmatch_t pmatch1[1]; + regmatch_t pmatch2[1]; + regoff_t off1 = 0; + regoff_t off2 = 0; + + bool has_match1 = false; + bool has_match2 = false; + int rc = 0; + if (version1 == version2) { return 0; } - if (pcmk__str_empty(version1) && pcmk__str_empty(version2)) { - return 0; - } - if (pcmk__str_empty(version1)) { - return -1; + + // Parse each version string as digit segments delimited by dots + pcmk__assert(regcomp(®ex, "^[[:digit:]]+\\.?", REG_EXTENDED) == 0); + + if (version1 != NULL) { + // Skip leading whitespace + for (; isspace(*version1); version1++); + has_match1 = (regexec(®ex, version1, 1, pmatch1, 0) == 0); } - if (pcmk__str_empty(version2)) { - return 1; + if (version2 != NULL) { + for (; isspace(*version2); version2++); + has_match2 = (regexec(®ex, version2, 1, pmatch2, 0) == 0); } - // Skip leading whitespace - for (; isspace(*version1); version1++); - for (; isspace(*version2); version2++); + while (has_match1 || has_match2) { + long long value1 = 0; + long long value2 = 0; - for (const char *v1 = version1, *v2 = version2; - ((*v1 != '\0') || (*v2 != '\0')); ) { - - long digit1 = parse_version_segment(&v1); - long digit2 = parse_version_segment(&v2); + if (has_match1) { + value1 = strtoll(version1 + off1 + pmatch1[0].rm_so, NULL, 10); + } + if (has_match2) { + value2 = strtoll(version2 + off2 + pmatch2[0].rm_so, NULL, 10); + } - if (digit1 < digit2) { + if (value1 < value2) { crm_trace("%s < %s", version1, version2); - return -1; + rc = -1; + goto done; } - if (digit1 > digit2) { + if (value1 > value2) { crm_trace("%s > %s", version1, version2); - return 1; + rc = 1; + goto done; + } + + // Compare next segments + if (has_match1) { + off1 += pmatch1[0].rm_eo; + has_match1 = (regexec(®ex, version1 + off1, 1, pmatch1, 0) == 0); + } + if (has_match2) { + off2 += pmatch2[0].rm_eo; + has_match2 = (regexec(®ex, version2 + off2, 1, pmatch2, 0) == 0); } } + crm_trace("%s == %s", version1, version2); - return 0; + +done: + regfree(®ex); + return rc; } /* * 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; } /*! * \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 " QB_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) } /* @FIXME uuid.h is an optional header per configure.ac, and we include it * conditionally above. But uuid_generate() and uuid_unparse() depend on it, on * many or perhaps all systems with libuuid. So it's not clear how it would ever * be optional in practice. * * Note that these functions are not POSIX, although there is probably no good * portable alternative. * * We do list libuuid as a build dependency in INSTALL.md already. */ #ifdef HAVE_UUID_UUID_H #include #endif // HAVE_UUID_UUID_H /*! * \internal * \brief Generate a 37-byte (36 bytes plus null terminator) UUID string * * \return Newly allocated UUID string * * \note The caller is responsible for freeing the return value using \c free(). */ char * pcmk__generate_uuid(void) { uuid_t uuid; // uuid_unparse() converts a UUID to a 37-byte string (including null byte) char *buffer = pcmk__assert_alloc(37, sizeof(char)); uuid_generate(uuid); uuid_unparse(uuid, buffer); return buffer; } /*! * \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 } /*! * \internal * \brief Add a timer * * \param[in] interval_ms The interval for the function to be called, in ms * \param[in] fn The function to be called * \param[in] data Data to be passed to fn (can be NULL) * * \return The ID of the event source */ guint pcmk__create_timer(guint interval_ms, GSourceFunc fn, gpointer data) { pcmk__assert(interval_ms != 0 && fn != NULL); if (interval_ms % 1000 == 0) { /* In case interval_ms is 0, the call to pcmk__timeout_ms2s ensures * an interval of one second. */ return g_timeout_add_seconds(pcmk__timeout_ms2s(interval_ms), fn, data); } else { return g_timeout_add(interval_ms, fn, data); } } /*! * \internal * \brief Convert milliseconds to seconds * * \param[in] timeout_ms The interval, in ms * * \return If \p timeout_ms is 0, return 0. Otherwise, return the number of * seconds, rounded to the nearest integer, with a minimum of 1. */ guint pcmk__timeout_ms2s(guint timeout_ms) { guint quot, rem; if (timeout_ms == 0) { return 0; } else if (timeout_ms < 1000) { return 1; } quot = timeout_ms / 1000; rem = timeout_ms % 1000; if (rem >= 500) { quot += 1; } return quot; } // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START #include static void _gnutls_log_func(int level, const char *msg) { crm_trace("%s", msg); } void crm_gnutls_global_init(void) { signal(SIGPIPE, SIG_IGN); gnutls_global_init(); gnutls_global_set_log_level(8); gnutls_global_set_log_function(_gnutls_log_func); } /*! * \brief Check whether string represents a client name used by cluster daemons * * \param[in] name String to check * * \return true if name is standard client name used by daemons, false otherwise * * \note This is provided by the client, and so cannot be used by itself as a * secure means of authentication. */ bool crm_is_daemon_name(const char *name) { return pcmk__str_any_of(name, "attrd", CRM_SYSTEM_CIB, CRM_SYSTEM_CRMD, CRM_SYSTEM_DC, CRM_SYSTEM_LRMD, CRM_SYSTEM_MCP, CRM_SYSTEM_PENGINE, CRM_SYSTEM_TENGINE, "pacemaker-attrd", "pacemaker-based", "pacemaker-controld", "pacemaker-execd", "pacemaker-fenced", "pacemaker-remoted", "pacemaker-schedulerd", "stonith-ng", "stonithd", NULL); } char * crm_generate_uuid(void) { return pcmk__generate_uuid(); } #define PW_BUFFER_LEN 500 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, PW_BUFFER_LEN); if (buffer == NULL) { return -ENOMEM; } rc = getpwnam_r(name, &pwd, buffer, 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; } 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; } // LCOV_EXCL_STOP // End deprecated API