/src/pacemaker/lib/common/utils.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2004-2026 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 <stdbool.h> |
16 | | #include <stdio.h> |
17 | | #include <unistd.h> |
18 | | #include <string.h> |
19 | | #include <stdlib.h> |
20 | | #include <limits.h> |
21 | | #include <pwd.h> |
22 | | #include <time.h> |
23 | | #include <libgen.h> |
24 | | #include <signal.h> |
25 | | #include <grp.h> |
26 | | |
27 | | #include <qb/qbdefs.h> |
28 | | |
29 | | #include <crm/crm.h> |
30 | | #include <crm/services.h> |
31 | | #include <crm/cib/internal.h> |
32 | | #include <crm/common/xml.h> |
33 | | #include <crm/common/util.h> |
34 | | #include <crm/common/ipc.h> |
35 | | #include <crm/common/iso8601.h> |
36 | | #include <crm/common/mainloop.h> |
37 | | #include <libxml/parser.h> // xmlCleanupParser() |
38 | | #include <libxml2/libxml/relaxng.h> |
39 | | |
40 | | #include "crmcommon_private.h" |
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) |
55 | 0 | { |
56 | | // @TODO This isn't really everything, move all cleanup here |
57 | 0 | mainloop_cleanup(); |
58 | 0 | pcmk__schema_cleanup(); |
59 | 0 | crm_log_deinit(); |
60 | | |
61 | | // Clean up external library global state |
62 | 0 | qb_log_fini(); // Don't log anything after this point |
63 | 0 | xmlCleanupParser(); |
64 | 0 | } |
65 | | |
66 | | bool |
67 | | pcmk__is_user_in_group(const char *user, const char *group) |
68 | 0 | { |
69 | 0 | struct group *grent; |
70 | 0 | char **gr_mem; |
71 | |
|
72 | 0 | if (user == NULL || group == NULL) { |
73 | 0 | return false; |
74 | 0 | } |
75 | | |
76 | 0 | setgrent(); |
77 | 0 | while ((grent = getgrent()) != NULL) { |
78 | 0 | if (grent->gr_mem == NULL) { |
79 | 0 | continue; |
80 | 0 | } |
81 | | |
82 | 0 | if(strcmp(group, grent->gr_name) != 0) { |
83 | 0 | continue; |
84 | 0 | } |
85 | | |
86 | 0 | gr_mem = grent->gr_mem; |
87 | 0 | while (*gr_mem != NULL) { |
88 | 0 | if (!strcmp(user, *gr_mem++)) { |
89 | 0 | endgrent(); |
90 | 0 | return true; |
91 | 0 | } |
92 | 0 | } |
93 | 0 | } |
94 | 0 | endgrent(); |
95 | 0 | return false; |
96 | 0 | } |
97 | | |
98 | | int |
99 | | pcmk__lookup_user(const char *name, uid_t *uid, gid_t *gid) |
100 | 0 | { |
101 | 0 | struct passwd *pwentry = NULL; |
102 | |
|
103 | 0 | CRM_CHECK(name != NULL, return EINVAL); |
104 | | |
105 | | // getpwnam() is not thread-safe, but Pacemaker is single-threaded |
106 | 0 | errno = 0; |
107 | 0 | pwentry = getpwnam(name); |
108 | 0 | if (pwentry == NULL) { |
109 | | /* Either an error occurred or no passwd entry was found. |
110 | | * |
111 | | * The value of errno is implementation-dependent if no passwd entry is |
112 | | * found. The POSIX specification does not consider it an error. |
113 | | * POSIX.1-2008 specifies that errno shall not be changed in this case, |
114 | | * while POSIX.1-2001 does not specify the value of errno in this case. |
115 | | * The man page on Linux notes that a variety of values have been |
116 | | * observed in practice. So an implementation may set errno to an |
117 | | * arbitrary value, despite the POSIX specification. |
118 | | * |
119 | | * However, if pwentry == NULL and errno == 0, then we know that no |
120 | | * matching entry was found and there was no error. So we default to |
121 | | * ENOENT as our return code. |
122 | | */ |
123 | 0 | return ((errno != 0)? errno : ENOENT); |
124 | 0 | } |
125 | | |
126 | 0 | if (uid != NULL) { |
127 | 0 | *uid = pwentry->pw_uid; |
128 | 0 | } |
129 | 0 | if (gid != NULL) { |
130 | 0 | *gid = pwentry->pw_gid; |
131 | 0 | } |
132 | 0 | pcmk__trace("User %s has uid=%lld gid=%lld", name, |
133 | 0 | (long long) pwentry->pw_uid, (long long) pwentry->pw_gid); |
134 | | |
135 | 0 | return pcmk_rc_ok; |
136 | 0 | } |
137 | | |
138 | | /*! |
139 | | * \internal |
140 | | * \brief Get user and group IDs of Pacemaker daemon user |
141 | | * |
142 | | * \param[out] uid Where to store daemon user ID (can be \c NULL) |
143 | | * \param[out] gid Where to store daemon group ID (can be \c NULL) |
144 | | * |
145 | | * \return Standard Pacemaker return code |
146 | | */ |
147 | | int |
148 | | pcmk__daemon_user(uid_t *uid, gid_t *gid) |
149 | 0 | { |
150 | 0 | static uid_t daemon_uid = 0; |
151 | 0 | static gid_t daemon_gid = 0; |
152 | 0 | static bool found = false; |
153 | |
|
154 | 0 | if (!found) { |
155 | 0 | int rc = pcmk__lookup_user(CRM_DAEMON_USER, &daemon_uid, &daemon_gid); |
156 | |
|
157 | 0 | if (rc != pcmk_rc_ok) { |
158 | 0 | return rc; |
159 | 0 | } |
160 | 0 | found = true; |
161 | 0 | } |
162 | | |
163 | 0 | if (uid != NULL) { |
164 | 0 | *uid = daemon_uid; |
165 | 0 | } |
166 | 0 | if (gid != NULL) { |
167 | 0 | *gid = daemon_gid; |
168 | 0 | } |
169 | 0 | return pcmk_rc_ok; |
170 | 0 | } |
171 | | |
172 | | /*! |
173 | | * \internal |
174 | | * \brief Compare two version strings to determine which one is higher |
175 | | * |
176 | | * A valid version string is of the form specified by the regex |
177 | | * <tt>[0-9]+(\.[0-9]+)*</tt>. |
178 | | * |
179 | | * Leading whitespace and trailing garbage are allowed and ignored. Anything |
180 | | * that doesn't match the regex above is considered garbage. |
181 | | * |
182 | | * For each string, we get all segments until the first invalid character. A |
183 | | * segment is a series of digits, and segments are delimited by a single dot. |
184 | | * The two strings are compared segment by segment, until either we find a |
185 | | * difference or we've processed all segments in both strings. |
186 | | * |
187 | | * If one string runs out of segments to compare before the other string does, |
188 | | * we treat it as if it has enough padding \c "0" segments to finish the |
189 | | * comparisons. |
190 | | * |
191 | | * Segments are compared by calling \c strtoll() to parse them to long long |
192 | | * integers and then performing standard integer comparison. |
193 | | * |
194 | | * \param[in] version1 First version to compare |
195 | | * \param[in] version2 Second version to compare |
196 | | * |
197 | | * \retval -1 if \p version1 evaluates to a lower version than \p version2 |
198 | | * \retval 1 if \p version1 evaluates to a higher version than \p version2 |
199 | | * \retval 0 if \p version1 and \p version2 evaluate to an equal version |
200 | | * |
201 | | * \note Each version segment's parsed value must fit into a <tt>long long</tt>. |
202 | | */ |
203 | | int |
204 | | pcmk__compare_versions(const char *version1, const char *version2) |
205 | 25 | { |
206 | 25 | int rc = 0; |
207 | 25 | gchar *match1 = NULL; |
208 | 25 | gchar *match2 = NULL; |
209 | 25 | gchar **segments1 = NULL; |
210 | 25 | gchar **segments2 = NULL; |
211 | 25 | GRegex *regex = NULL; |
212 | | |
213 | 25 | if (pcmk__str_eq(version1, version2, pcmk__str_none)) { |
214 | 0 | goto done; |
215 | 0 | } |
216 | | |
217 | | // Ignore leading whitespace and trailing garbage |
218 | 25 | regex = g_regex_new("^\\s*(\\d+(?:\\.\\d+)*)", 0, 0, NULL); |
219 | | |
220 | 25 | if (!pcmk__str_empty(version1)) { |
221 | 25 | GMatchInfo *match_info = NULL; |
222 | | |
223 | 25 | if (g_regex_match(regex, version1, 0, &match_info)) { |
224 | 25 | match1 = g_match_info_fetch(match_info, 1); |
225 | 25 | } |
226 | 25 | g_match_info_unref(match_info); |
227 | 25 | } |
228 | 25 | if (!pcmk__str_empty(version2)) { |
229 | 25 | GMatchInfo *match_info = NULL; |
230 | | |
231 | 25 | if (g_regex_match(regex, version2, 0, &match_info)) { |
232 | 25 | match2 = g_match_info_fetch(match_info, 1); |
233 | 25 | } |
234 | 25 | g_match_info_unref(match_info); |
235 | 25 | } |
236 | | |
237 | 25 | segments1 = g_strsplit(pcmk__s(match1, ""), ".", 0); |
238 | 25 | segments2 = g_strsplit(pcmk__s(match2, ""), ".", 0); |
239 | | |
240 | 25 | for (gchar **segment1 = segments1, **segment2 = segments2; |
241 | 51 | (*segment1 != NULL) || (*segment2 != NULL); ) { |
242 | | |
243 | 51 | long long value1 = 0; |
244 | 51 | long long value2 = 0; |
245 | | |
246 | 51 | if (*segment1 != NULL) { |
247 | | // Make Coverity happy by casting to void |
248 | 51 | (void) pcmk__scan_ll(*segment1, &value1, 0); |
249 | 51 | segment1++; |
250 | 51 | } |
251 | 51 | if (*segment2 != NULL) { |
252 | 51 | (void) pcmk__scan_ll(*segment2, &value2, 0); |
253 | 51 | segment2++; |
254 | 51 | } |
255 | | |
256 | 51 | if (value1 < value2) { |
257 | 9 | pcmk__trace("%s < %s", version1, version2); |
258 | 9 | rc = -1; |
259 | 9 | goto done; |
260 | 9 | } |
261 | 42 | if (value1 > value2) { |
262 | 16 | pcmk__trace("%s > %s", version1, version2); |
263 | 16 | rc = 1; |
264 | 16 | goto done; |
265 | 16 | } |
266 | 42 | } |
267 | | |
268 | 0 | pcmk__trace("%s == %s", version1, version2); |
269 | | |
270 | 25 | done: |
271 | 25 | g_free(match1); |
272 | 25 | g_free(match2); |
273 | 25 | g_strfreev(segments1); |
274 | 25 | g_strfreev(segments2); |
275 | 25 | if (regex != NULL) { |
276 | 25 | g_regex_unref(regex); |
277 | 25 | } |
278 | 25 | return rc; |
279 | 0 | } |
280 | | |
281 | | /* @FIXME uuid.h is an optional header per configure.ac, and we include it |
282 | | * conditionally above. But uuid_generate() and uuid_unparse() depend on it, on |
283 | | * many or perhaps all systems with libuuid. So it's not clear how it would ever |
284 | | * be optional in practice. |
285 | | * |
286 | | * Note that these functions are not POSIX, although there is probably no good |
287 | | * portable alternative. |
288 | | * |
289 | | * We do list libuuid as a build dependency in INSTALL.md already. |
290 | | */ |
291 | | |
292 | | #ifdef HAVE_UUID_UUID_H |
293 | | #include <uuid/uuid.h> |
294 | | #endif // HAVE_UUID_UUID_H |
295 | | |
296 | | /*! |
297 | | * \internal |
298 | | * \brief Generate a 37-byte (36 bytes plus null terminator) UUID string |
299 | | * |
300 | | * \return Newly allocated UUID string |
301 | | * |
302 | | * \note The caller is responsible for freeing the return value using \c free(). |
303 | | */ |
304 | | char * |
305 | | pcmk__generate_uuid(void) |
306 | 23 | { |
307 | 23 | uuid_t uuid; |
308 | | |
309 | | // uuid_unparse() converts a UUID to a 37-byte string (including null byte) |
310 | 23 | char *buffer = pcmk__assert_alloc(37, sizeof(char)); |
311 | | |
312 | 23 | uuid_generate(uuid); |
313 | 23 | uuid_unparse(uuid, buffer); |
314 | 23 | return buffer; |
315 | 23 | } |
316 | | |
317 | | /*! |
318 | | * \internal |
319 | | * \brief Sleep for given milliseconds |
320 | | * |
321 | | * \param[in] ms Time to sleep |
322 | | * |
323 | | * \note The full time might not be slept if a signal is received. |
324 | | */ |
325 | | void |
326 | | pcmk__sleep_ms(unsigned int ms) |
327 | 0 | { |
328 | | // @TODO Impose a sane maximum sleep to avoid hanging a process for long |
329 | | //CRM_CHECK(ms <= MAX_SLEEP, ms = MAX_SLEEP); |
330 | | |
331 | | // Use sleep() for any whole seconds |
332 | 0 | if (ms >= 1000) { |
333 | 0 | sleep(ms / 1000); |
334 | 0 | ms -= ms / 1000; |
335 | 0 | } |
336 | |
|
337 | 0 | if (ms == 0) { |
338 | 0 | return; |
339 | 0 | } |
340 | | |
341 | 0 | #if defined(HAVE_NANOSLEEP) |
342 | | // nanosleep() is POSIX-2008, so prefer that |
343 | 0 | { |
344 | 0 | struct timespec req = { .tv_sec = 0, .tv_nsec = (long) (ms * 1000000) }; |
345 | |
|
346 | 0 | nanosleep(&req, NULL); |
347 | 0 | } |
348 | | #elif defined(HAVE_USLEEP) |
349 | | // usleep() is widely available, though considered obsolete |
350 | | usleep((useconds_t) ms); |
351 | | #else |
352 | | // Otherwise use a trick with select() timeout |
353 | | { |
354 | | struct timeval tv = { .tv_sec = 0, .tv_usec = (suseconds_t) ms }; |
355 | | |
356 | | select(0, NULL, NULL, NULL, &tv); |
357 | | } |
358 | | #endif |
359 | 0 | } |
360 | | |
361 | | /*! |
362 | | * \internal |
363 | | * \brief Add a timer |
364 | | * |
365 | | * \param[in] interval_ms The interval for the function to be called, in ms |
366 | | * \param[in] fn The function to be called |
367 | | * \param[in] data Data to be passed to fn (can be NULL) |
368 | | * |
369 | | * \return The ID of the event source |
370 | | * |
371 | | * \note If \p fn returns \c G_SOURCE_CONTINUE, then it will be called again |
372 | | * after \p interval_ms. If \p fn returns \c G_SOURCE_REMOVE, then the |
373 | | * timeout is destroyed and \c fn will not be called again. Note that no |
374 | | * \c GDestroyNotify function is set (see \c g_timeout_add_full() and |
375 | | * \c g_timeout_add_seconds_full()), so only the timeout is destroyed. |
376 | | * \p data is left intact. |
377 | | */ |
378 | | unsigned int |
379 | | pcmk__create_timer(unsigned int interval_ms, GSourceFunc fn, void *data) |
380 | 0 | { |
381 | 0 | pcmk__assert(interval_ms != 0 && fn != NULL); |
382 | |
|
383 | 0 | if (interval_ms % 1000 == 0) { |
384 | | /* In case interval_ms is 0, the call to pcmk__timeout_ms2s ensures |
385 | | * an interval of one second. |
386 | | */ |
387 | 0 | return g_timeout_add_seconds(pcmk__timeout_ms2s(interval_ms), fn, data); |
388 | 0 | } else { |
389 | 0 | return g_timeout_add(interval_ms, fn, data); |
390 | 0 | } |
391 | 0 | } |
392 | | |
393 | | /*! |
394 | | * \internal |
395 | | * \brief Convert milliseconds to seconds |
396 | | * |
397 | | * \param[in] timeout_ms The interval, in ms |
398 | | * |
399 | | * \return If \p timeout_ms is 0, return 0. Otherwise, return the number of |
400 | | * seconds, rounded to the nearest integer, with a minimum of 1. |
401 | | */ |
402 | | unsigned int |
403 | | pcmk__timeout_ms2s(unsigned int timeout_ms) |
404 | 0 | { |
405 | 0 | unsigned int quot = 0; |
406 | 0 | unsigned int rem = 0; |
407 | |
|
408 | 0 | if (timeout_ms == 0) { |
409 | 0 | return 0; |
410 | 0 | } else if (timeout_ms < 1000) { |
411 | 0 | return 1; |
412 | 0 | } |
413 | | |
414 | 0 | quot = timeout_ms / 1000; |
415 | 0 | rem = timeout_ms % 1000; |
416 | |
|
417 | 0 | if (rem >= 500) { |
418 | 0 | quot += 1; |
419 | 0 | } |
420 | |
|
421 | 0 | return quot; |
422 | 0 | } |
423 | | |
424 | | // Deprecated functions kept only for backward API compatibility |
425 | | // LCOV_EXCL_START |
426 | | |
427 | | #include <gnutls/gnutls.h> // gnutls_global_init(), etc. |
428 | | |
429 | | #include <crm/common/util_compat.h> |
430 | | |
431 | | static void |
432 | | _gnutls_log_func(int level, const char *msg) |
433 | 0 | { |
434 | 0 | pcmk__trace("%s", msg); |
435 | 0 | } |
436 | | |
437 | | void |
438 | | crm_gnutls_global_init(void) |
439 | 0 | { |
440 | 0 | signal(SIGPIPE, SIG_IGN); |
441 | 0 | gnutls_global_init(); |
442 | 0 | gnutls_global_set_log_level(8); |
443 | 0 | gnutls_global_set_log_function(_gnutls_log_func); |
444 | 0 | } |
445 | | |
446 | | /*! |
447 | | * \brief Check whether string represents a client name used by cluster daemons |
448 | | * |
449 | | * \param[in] name String to check |
450 | | * |
451 | | * \return true if name is standard client name used by daemons, false otherwise |
452 | | * |
453 | | * \note This is provided by the client, and so cannot be used by itself as a |
454 | | * secure means of authentication. |
455 | | */ |
456 | | bool |
457 | | crm_is_daemon_name(const char *name) |
458 | 0 | { |
459 | 0 | return pcmk__str_any_of(name, |
460 | 0 | "attrd", |
461 | 0 | CRM_SYSTEM_CIB, |
462 | 0 | CRM_SYSTEM_CRMD, |
463 | 0 | CRM_SYSTEM_DC, |
464 | 0 | CRM_SYSTEM_LRMD, |
465 | 0 | CRM_SYSTEM_MCP, |
466 | 0 | CRM_SYSTEM_PENGINE, |
467 | 0 | CRM_SYSTEM_TENGINE, |
468 | 0 | "pacemaker-attrd", |
469 | 0 | "pacemaker-based", |
470 | 0 | "pacemaker-controld", |
471 | 0 | "pacemaker-execd", |
472 | 0 | "pacemaker-fenced", |
473 | 0 | "pacemaker-remoted", |
474 | 0 | "pacemaker-schedulerd", |
475 | 0 | "stonith-ng", |
476 | 0 | "stonithd", |
477 | 0 | NULL); |
478 | 0 | } |
479 | | |
480 | | char * |
481 | | crm_generate_uuid(void) |
482 | 0 | { |
483 | 0 | return pcmk__generate_uuid(); |
484 | 0 | } |
485 | | |
486 | 0 | #define PW_BUFFER_LEN 500 |
487 | | |
488 | | int |
489 | | crm_user_lookup(const char *name, uid_t * uid, gid_t * gid) |
490 | 0 | { |
491 | 0 | int rc = pcmk_ok; |
492 | 0 | char *buffer = NULL; |
493 | 0 | struct passwd pwd; |
494 | 0 | struct passwd *pwentry = NULL; |
495 | |
|
496 | 0 | buffer = calloc(1, PW_BUFFER_LEN); |
497 | 0 | if (buffer == NULL) { |
498 | 0 | return -ENOMEM; |
499 | 0 | } |
500 | | |
501 | 0 | rc = getpwnam_r(name, &pwd, buffer, PW_BUFFER_LEN, &pwentry); |
502 | 0 | if (pwentry) { |
503 | 0 | if (uid) { |
504 | 0 | *uid = pwentry->pw_uid; |
505 | 0 | } |
506 | 0 | if (gid) { |
507 | 0 | *gid = pwentry->pw_gid; |
508 | 0 | } |
509 | 0 | pcmk__trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, |
510 | 0 | pwentry->pw_gid); |
511 | |
|
512 | 0 | } else { |
513 | 0 | rc = rc? -rc : -EINVAL; |
514 | 0 | pcmk__info("User %s lookup: %s", name, pcmk_strerror(rc)); |
515 | 0 | } |
516 | | |
517 | 0 | free(buffer); |
518 | 0 | return rc; |
519 | 0 | } |
520 | | |
521 | | int |
522 | | pcmk_daemon_user(uid_t *uid, gid_t *gid) |
523 | 0 | { |
524 | 0 | static uid_t daemon_uid; |
525 | 0 | static gid_t daemon_gid; |
526 | 0 | static bool found = false; |
527 | 0 | int rc = pcmk_ok; |
528 | |
|
529 | 0 | if (!found) { |
530 | 0 | rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid); |
531 | 0 | if (rc == pcmk_ok) { |
532 | 0 | found = true; |
533 | 0 | } |
534 | 0 | } |
535 | 0 | if (found) { |
536 | 0 | if (uid) { |
537 | 0 | *uid = daemon_uid; |
538 | 0 | } |
539 | 0 | if (gid) { |
540 | 0 | *gid = daemon_gid; |
541 | 0 | } |
542 | 0 | } |
543 | 0 | return rc; |
544 | 0 | } |
545 | | |
546 | | static int |
547 | | version_helper(const char *text, const char **end_text) |
548 | 0 | { |
549 | 0 | int atoi_result = -1; |
550 | |
|
551 | 0 | pcmk__assert(end_text != NULL); |
552 | |
|
553 | 0 | errno = 0; |
554 | |
|
555 | 0 | if (text != NULL && text[0] != 0) { |
556 | | /* seemingly sacrificing const-correctness -- because while strtol |
557 | | doesn't modify the input, it doesn't want to artificially taint the |
558 | | "end_text" pointer-to-pointer-to-first-char-in-string with constness |
559 | | in case the input wasn't actually constant -- by semantic definition |
560 | | not a single character will get modified so it shall be perfectly |
561 | | safe to make compiler happy with dropping "const" qualifier here */ |
562 | 0 | atoi_result = (int) strtol(text, (char **) end_text, 10); |
563 | |
|
564 | 0 | if (errno == EINVAL) { |
565 | 0 | pcmk__err("Conversion of '%s' %c failed", text, text[0]); |
566 | 0 | atoi_result = -1; |
567 | 0 | } |
568 | 0 | } |
569 | 0 | return atoi_result; |
570 | 0 | } |
571 | | |
572 | | int |
573 | | compare_version(const char *version1, const char *version2) |
574 | 0 | { |
575 | 0 | int rc = 0; |
576 | 0 | int lpc = 0; |
577 | 0 | const char *ver1_iter, *ver2_iter; |
578 | |
|
579 | 0 | if (version1 == NULL && version2 == NULL) { |
580 | 0 | return 0; |
581 | 0 | } else if (version1 == NULL) { |
582 | 0 | return -1; |
583 | 0 | } else if (version2 == NULL) { |
584 | 0 | return 1; |
585 | 0 | } |
586 | | |
587 | 0 | ver1_iter = version1; |
588 | 0 | ver2_iter = version2; |
589 | |
|
590 | 0 | while (1) { |
591 | 0 | int digit1 = 0; |
592 | 0 | int digit2 = 0; |
593 | |
|
594 | 0 | lpc++; |
595 | |
|
596 | 0 | if (ver1_iter == ver2_iter) { |
597 | 0 | break; |
598 | 0 | } |
599 | | |
600 | 0 | if (ver1_iter != NULL) { |
601 | 0 | digit1 = version_helper(ver1_iter, &ver1_iter); |
602 | 0 | } |
603 | |
|
604 | 0 | if (ver2_iter != NULL) { |
605 | 0 | digit2 = version_helper(ver2_iter, &ver2_iter); |
606 | 0 | } |
607 | |
|
608 | 0 | if (digit1 < digit2) { |
609 | 0 | rc = -1; |
610 | 0 | break; |
611 | |
|
612 | 0 | } else if (digit1 > digit2) { |
613 | 0 | rc = 1; |
614 | 0 | break; |
615 | 0 | } |
616 | | |
617 | 0 | if (ver1_iter != NULL && *ver1_iter == '.') { |
618 | 0 | ver1_iter++; |
619 | 0 | } |
620 | 0 | if (ver1_iter != NULL && *ver1_iter == '\0') { |
621 | 0 | ver1_iter = NULL; |
622 | 0 | } |
623 | |
|
624 | 0 | if (ver2_iter != NULL && *ver2_iter == '.') { |
625 | 0 | ver2_iter++; |
626 | 0 | } |
627 | 0 | if (ver2_iter != NULL && *ver2_iter == 0) { |
628 | 0 | ver2_iter = NULL; |
629 | 0 | } |
630 | 0 | } |
631 | |
|
632 | 0 | if (rc == 0) { |
633 | 0 | pcmk__trace("%s == %s (%d)", version1, version2, lpc); |
634 | 0 | } else if (rc < 0) { |
635 | 0 | pcmk__trace("%s < %s (%d)", version1, version2, lpc); |
636 | 0 | } else if (rc > 0) { |
637 | 0 | pcmk__trace("%s > %s (%d)", version1, version2, lpc); |
638 | 0 | } |
639 | | |
640 | 0 | return rc; |
641 | 0 | } |
642 | | |
643 | | // LCOV_EXCL_STOP |
644 | | // End deprecated API |