Line data Source code
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/types.h>
17 : #include <sys/wait.h>
18 : #include <sys/stat.h>
19 : #include <sys/utsname.h>
20 :
21 : #include <stdio.h>
22 : #include <unistd.h>
23 : #include <string.h>
24 : #include <stdlib.h>
25 : #include <limits.h>
26 : #include <pwd.h>
27 : #include <time.h>
28 : #include <libgen.h>
29 : #include <signal.h>
30 : #include <grp.h>
31 :
32 : #include <qb/qbdefs.h>
33 :
34 : #include <crm/crm.h>
35 : #include <crm/services.h>
36 : #include <crm/cib/internal.h>
37 : #include <crm/common/xml.h>
38 : #include <crm/common/util.h>
39 : #include <crm/common/ipc.h>
40 : #include <crm/common/iso8601.h>
41 : #include <crm/common/mainloop.h>
42 : #include <libxml2/libxml/relaxng.h>
43 :
44 : #include "crmcommon_private.h"
45 :
46 : CRM_TRACE_INIT_DATA(common);
47 :
48 : gboolean crm_config_error = FALSE;
49 : gboolean crm_config_warning = FALSE;
50 : char *crm_system_name = NULL;
51 :
52 : bool
53 5 : pcmk__is_user_in_group(const char *user, const char *group)
54 : {
55 : struct group *grent;
56 : char **gr_mem;
57 :
58 5 : if (user == NULL || group == NULL) {
59 2 : return false;
60 : }
61 :
62 3 : setgrent();
63 9 : while ((grent = getgrent()) != NULL) {
64 7 : if (grent->gr_mem == NULL) {
65 0 : continue;
66 : }
67 :
68 7 : if(strcmp(group, grent->gr_name) != 0) {
69 5 : continue;
70 : }
71 :
72 2 : gr_mem = grent->gr_mem;
73 4 : while (*gr_mem != NULL) {
74 3 : if (!strcmp(user, *gr_mem++)) {
75 1 : endgrent();
76 1 : return true;
77 : }
78 : }
79 : }
80 2 : endgrent();
81 2 : return false;
82 : }
83 :
84 : int
85 7 : crm_user_lookup(const char *name, uid_t * uid, gid_t * gid)
86 : {
87 7 : int rc = pcmk_ok;
88 7 : char *buffer = NULL;
89 : struct passwd pwd;
90 7 : struct passwd *pwentry = NULL;
91 :
92 7 : buffer = calloc(1, PCMK__PW_BUFFER_LEN);
93 7 : if (buffer == NULL) {
94 1 : return -ENOMEM;
95 : }
96 :
97 6 : rc = getpwnam_r(name, &pwd, buffer, PCMK__PW_BUFFER_LEN, &pwentry);
98 6 : if (pwentry) {
99 3 : if (uid) {
100 2 : *uid = pwentry->pw_uid;
101 : }
102 3 : if (gid) {
103 2 : *gid = pwentry->pw_gid;
104 : }
105 3 : crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid);
106 :
107 : } else {
108 3 : rc = rc? -rc : -EINVAL;
109 3 : crm_info("User %s lookup: %s", name, pcmk_strerror(rc));
110 : }
111 :
112 6 : free(buffer);
113 6 : return rc;
114 : }
115 :
116 : /*!
117 : * \brief Get user and group IDs of pacemaker daemon user
118 : *
119 : * \param[out] uid If non-NULL, where to store daemon user ID
120 : * \param[out] gid If non-NULL, where to store daemon group ID
121 : *
122 : * \return pcmk_ok on success, -errno otherwise
123 : */
124 : int
125 3 : pcmk_daemon_user(uid_t *uid, gid_t *gid)
126 : {
127 : static uid_t daemon_uid;
128 : static gid_t daemon_gid;
129 : static bool found = false;
130 3 : int rc = pcmk_ok;
131 :
132 3 : if (!found) {
133 2 : rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid);
134 2 : if (rc == pcmk_ok) {
135 1 : found = true;
136 : }
137 : }
138 3 : if (found) {
139 2 : if (uid) {
140 1 : *uid = daemon_uid;
141 : }
142 2 : if (gid) {
143 1 : *gid = daemon_gid;
144 : }
145 : }
146 3 : return rc;
147 : }
148 :
149 : /*!
150 : * \internal
151 : * \brief Return the integer equivalent of a portion of a string
152 : *
153 : * \param[in] text Pointer to beginning of string portion
154 : * \param[out] end_text This will point to next character after integer
155 : */
156 : static int
157 76 : version_helper(const char *text, const char **end_text)
158 : {
159 76 : int atoi_result = -1;
160 :
161 76 : CRM_ASSERT(end_text != NULL);
162 :
163 76 : errno = 0;
164 :
165 76 : if (text != NULL && text[0] != 0) {
166 : /* seemingly sacrificing const-correctness -- because while strtol
167 : doesn't modify the input, it doesn't want to artificially taint the
168 : "end_text" pointer-to-pointer-to-first-char-in-string with constness
169 : in case the input wasn't actually constant -- by semantic definition
170 : not a single character will get modified so it shall be perfectly
171 : safe to make compiler happy with dropping "const" qualifier here */
172 76 : atoi_result = (int) strtol(text, (char **) end_text, 10);
173 :
174 76 : if (errno == EINVAL) {
175 0 : crm_err("Conversion of '%s' %c failed", text, text[0]);
176 0 : atoi_result = -1;
177 : }
178 : }
179 76 : return atoi_result;
180 : }
181 :
182 : /*
183 : * version1 < version2 : -1
184 : * version1 = version2 : 0
185 : * version1 > version2 : 1
186 : */
187 : int
188 28 : compare_version(const char *version1, const char *version2)
189 : {
190 28 : int rc = 0;
191 28 : int lpc = 0;
192 : const char *ver1_iter, *ver2_iter;
193 :
194 28 : if (version1 == NULL && version2 == NULL) {
195 1 : return 0;
196 27 : } else if (version1 == NULL) {
197 2 : return -1;
198 25 : } else if (version2 == NULL) {
199 2 : return 1;
200 : }
201 :
202 23 : ver1_iter = version1;
203 23 : ver2_iter = version2;
204 :
205 23 : while (1) {
206 46 : int digit1 = 0;
207 46 : int digit2 = 0;
208 :
209 46 : lpc++;
210 :
211 46 : if (ver1_iter == ver2_iter) {
212 5 : break;
213 : }
214 :
215 41 : if (ver1_iter != NULL) {
216 39 : digit1 = version_helper(ver1_iter, &ver1_iter);
217 : }
218 :
219 41 : if (ver2_iter != NULL) {
220 37 : digit2 = version_helper(ver2_iter, &ver2_iter);
221 : }
222 :
223 41 : if (digit1 < digit2) {
224 10 : rc = -1;
225 10 : break;
226 :
227 31 : } else if (digit1 > digit2) {
228 8 : rc = 1;
229 8 : break;
230 : }
231 :
232 23 : if (ver1_iter != NULL && *ver1_iter == '.') {
233 19 : ver1_iter++;
234 : }
235 23 : if (ver1_iter != NULL && *ver1_iter == '\0') {
236 4 : ver1_iter = NULL;
237 : }
238 :
239 23 : if (ver2_iter != NULL && *ver2_iter == '.') {
240 17 : ver2_iter++;
241 : }
242 23 : if (ver2_iter != NULL && *ver2_iter == 0) {
243 4 : ver2_iter = NULL;
244 : }
245 : }
246 :
247 23 : if (rc == 0) {
248 5 : crm_trace("%s == %s (%d)", version1, version2, lpc);
249 18 : } else if (rc < 0) {
250 10 : crm_trace("%s < %s (%d)", version1, version2, lpc);
251 8 : } else if (rc > 0) {
252 8 : crm_trace("%s > %s (%d)", version1, version2, lpc);
253 : }
254 :
255 23 : return rc;
256 : }
257 :
258 : /*!
259 : * \internal
260 : * \brief Log a failed assertion
261 : *
262 : * \param[in] file File making the assertion
263 : * \param[in] function Function making the assertion
264 : * \param[in] line Line of file making the assertion
265 : * \param[in] assert_condition String representation of assertion
266 : */
267 : static void
268 70 : log_assertion_as(const char *file, const char *function, int line,
269 : const char *assert_condition)
270 : {
271 70 : if (!pcmk__is_daemon) {
272 70 : crm_enable_stderr(TRUE); // Make sure command-line user sees message
273 : }
274 70 : crm_err("%s: Triggered fatal assertion at %s:%d : %s",
275 : function, file, line, assert_condition);
276 70 : }
277 :
278 : /* coverity[+kill] */
279 : /*!
280 : * \internal
281 : * \brief Log a failed assertion and abort
282 : *
283 : * \param[in] file File making the assertion
284 : * \param[in] function Function making the assertion
285 : * \param[in] line Line of file making the assertion
286 : * \param[in] assert_condition String representation of assertion
287 : *
288 : * \note This does not return
289 : */
290 : static _Noreturn void
291 32 : abort_as(const char *file, const char *function, int line,
292 : const char *assert_condition)
293 : {
294 32 : log_assertion_as(file, function, line, assert_condition);
295 32 : abort();
296 : }
297 :
298 : /* coverity[+kill] */
299 : /*!
300 : * \internal
301 : * \brief Handle a failed assertion
302 : *
303 : * When called by a daemon, fork a child that aborts (to dump core), otherwise
304 : * abort the current process.
305 : *
306 : * \param[in] file File making the assertion
307 : * \param[in] function Function making the assertion
308 : * \param[in] line Line of file making the assertion
309 : * \param[in] assert_condition String representation of assertion
310 : */
311 : static void
312 0 : fail_assert_as(const char *file, const char *function, int line,
313 : const char *assert_condition)
314 : {
315 0 : int status = 0;
316 0 : pid_t pid = 0;
317 :
318 0 : if (!pcmk__is_daemon) {
319 0 : abort_as(file, function, line, assert_condition); // does not return
320 : }
321 :
322 0 : pid = fork();
323 0 : switch (pid) {
324 0 : case -1: // Fork failed
325 0 : crm_warn("%s: Cannot dump core for non-fatal assertion at %s:%d "
326 : ": %s", function, file, line, assert_condition);
327 0 : break;
328 :
329 0 : case 0: // Child process: just abort to dump core
330 0 : abort();
331 : break;
332 :
333 0 : default: // Parent process: wait for child
334 0 : crm_err("%s: Forked child [%d] to record non-fatal assertion at "
335 : "%s:%d : %s", function, pid, file, line, assert_condition);
336 0 : crm_write_blackbox(SIGTRAP, NULL);
337 : do {
338 0 : if (waitpid(pid, &status, 0) == pid) {
339 0 : return; // Child finished dumping core
340 : }
341 0 : } while (errno == EINTR);
342 0 : if (errno == ECHILD) {
343 : // crm_mon ignores SIGCHLD
344 0 : crm_trace("Cannot wait on forked child [%d] "
345 : "(SIGCHLD is probably ignored)", pid);
346 : } else {
347 0 : crm_err("Cannot wait on forked child [%d]: %s",
348 : pid, pcmk_rc_str(errno));
349 : }
350 0 : break;
351 : }
352 : }
353 :
354 : /* coverity[+kill] */
355 : void
356 0 : crm_abort(const char *file, const char *function, int line,
357 : const char *assert_condition, gboolean do_core, gboolean do_fork)
358 : {
359 0 : if (!do_fork) {
360 0 : abort_as(file, function, line, assert_condition);
361 0 : } else if (do_core) {
362 0 : fail_assert_as(file, function, line, assert_condition);
363 : } else {
364 0 : log_assertion_as(file, function, line, assert_condition);
365 : }
366 0 : }
367 :
368 : /*!
369 : * \internal
370 : * \brief Convert the current process to a daemon process
371 : *
372 : * Fork a child process, exit the parent, create a PID file with the current
373 : * process ID, and close the standard input/output/error file descriptors.
374 : * Exit instead if a daemon is already running and using the PID file.
375 : *
376 : * \param[in] name Daemon executable name
377 : * \param[in] pidfile File name to use as PID file
378 : */
379 : void
380 0 : pcmk__daemonize(const char *name, const char *pidfile)
381 : {
382 : int rc;
383 : pid_t pid;
384 :
385 : /* Check before we even try... */
386 0 : rc = pcmk__pidfile_matches(pidfile, 1, name, &pid);
387 0 : if ((rc != pcmk_rc_ok) && (rc != ENOENT)) {
388 0 : crm_err("%s: already running [pid %lld in %s]",
389 : name, (long long) pid, pidfile);
390 0 : printf("%s: already running [pid %lld in %s]\n",
391 : name, (long long) pid, pidfile);
392 0 : crm_exit(CRM_EX_ERROR);
393 : }
394 :
395 0 : pid = fork();
396 0 : if (pid < 0) {
397 0 : fprintf(stderr, "%s: could not start daemon\n", name);
398 0 : crm_perror(LOG_ERR, "fork");
399 0 : crm_exit(CRM_EX_OSERR);
400 :
401 0 : } else if (pid > 0) {
402 0 : crm_exit(CRM_EX_OK);
403 : }
404 :
405 0 : rc = pcmk__lock_pidfile(pidfile, name);
406 0 : if (rc != pcmk_rc_ok) {
407 0 : crm_err("Could not lock '%s' for %s: %s " CRM_XS " rc=%d",
408 : pidfile, name, pcmk_rc_str(rc), rc);
409 0 : printf("Could not lock '%s' for %s: %s (%d)\n",
410 : pidfile, name, pcmk_rc_str(rc), rc);
411 0 : crm_exit(CRM_EX_ERROR);
412 : }
413 :
414 0 : umask(S_IWGRP | S_IWOTH | S_IROTH);
415 :
416 0 : close(STDIN_FILENO);
417 0 : pcmk__open_devnull(O_RDONLY); // stdin (fd 0)
418 :
419 0 : close(STDOUT_FILENO);
420 0 : pcmk__open_devnull(O_WRONLY); // stdout (fd 1)
421 :
422 0 : close(STDERR_FILENO);
423 0 : pcmk__open_devnull(O_WRONLY); // stderr (fd 2)
424 0 : }
425 :
426 : #ifdef HAVE_UUID_UUID_H
427 : # include <uuid/uuid.h>
428 : #endif
429 :
430 : char *
431 0 : crm_generate_uuid(void)
432 : {
433 : unsigned char uuid[16];
434 0 : char *buffer = malloc(37); /* Including NUL byte */
435 :
436 0 : pcmk__mem_assert(buffer);
437 0 : uuid_generate(uuid);
438 0 : uuid_unparse(uuid, buffer);
439 0 : return buffer;
440 : }
441 :
442 : #ifdef HAVE_GNUTLS_GNUTLS_H
443 : void
444 0 : crm_gnutls_global_init(void)
445 : {
446 0 : signal(SIGPIPE, SIG_IGN);
447 0 : gnutls_global_init();
448 0 : }
449 : #endif
450 :
451 : bool
452 526 : pcmk_str_is_infinity(const char *s) {
453 526 : return pcmk__str_any_of(s, PCMK_VALUE_INFINITY, PCMK_VALUE_PLUS_INFINITY,
454 : NULL);
455 : }
456 :
457 : bool
458 458 : pcmk_str_is_minus_infinity(const char *s) {
459 458 : return pcmk__str_eq(s, PCMK_VALUE_MINUS_INFINITY, pcmk__str_none);
460 : }
461 :
462 : /*!
463 : * \internal
464 : * \brief Sleep for given milliseconds
465 : *
466 : * \param[in] ms Time to sleep
467 : *
468 : * \note The full time might not be slept if a signal is received.
469 : */
470 : void
471 0 : pcmk__sleep_ms(unsigned int ms)
472 : {
473 : // @TODO Impose a sane maximum sleep to avoid hanging a process for long
474 : //CRM_CHECK(ms <= MAX_SLEEP, ms = MAX_SLEEP);
475 :
476 : // Use sleep() for any whole seconds
477 0 : if (ms >= 1000) {
478 0 : sleep(ms / 1000);
479 0 : ms -= ms / 1000;
480 : }
481 :
482 0 : if (ms == 0) {
483 0 : return;
484 : }
485 :
486 : #if defined(HAVE_NANOSLEEP)
487 : // nanosleep() is POSIX-2008, so prefer that
488 : {
489 0 : struct timespec req = { .tv_sec = 0, .tv_nsec = (long) (ms * 1000000) };
490 :
491 0 : nanosleep(&req, NULL);
492 : }
493 : #elif defined(HAVE_USLEEP)
494 : // usleep() is widely available, though considered obsolete
495 : usleep((useconds_t) ms);
496 : #else
497 : // Otherwise use a trick with select() timeout
498 : {
499 : struct timeval tv = { .tv_sec = 0, .tv_usec = (suseconds_t) ms };
500 :
501 : select(0, NULL, NULL, NULL, &tv);
502 : }
503 : #endif
504 : }
505 :
506 : // Deprecated functions kept only for backward API compatibility
507 : // LCOV_EXCL_START
508 :
509 : #include <crm/common/util_compat.h>
510 :
511 : guint
512 : crm_parse_interval_spec(const char *input)
513 : {
514 : long long msec = -1;
515 :
516 : errno = 0;
517 : if (input == NULL) {
518 : return 0;
519 :
520 : } else if (input[0] == 'P') {
521 : crm_time_t *period_s = crm_time_parse_duration(input);
522 :
523 : if (period_s) {
524 : msec = 1000 * crm_time_get_seconds(period_s);
525 : crm_time_free(period_s);
526 : }
527 :
528 : } else {
529 : msec = crm_get_msec(input);
530 : }
531 :
532 : if (msec < 0) {
533 : crm_warn("Using 0 instead of '%s'", input);
534 : errno = EINVAL;
535 : return 0;
536 : }
537 : return (msec >= G_MAXUINT)? G_MAXUINT : (guint) msec;
538 : }
539 :
540 : char *
541 : pcmk_hostname(void)
542 : {
543 : struct utsname hostinfo;
544 :
545 : return (uname(&hostinfo) < 0)? NULL : strdup(hostinfo.nodename);
546 : }
547 :
548 : // LCOV_EXCL_STOP
549 : // End deprecated API
|