pacemaker 2.1.5-a3f44794f94
Scalable High-Availability cluster resource manager
utils.c
Go to the documentation of this file.
1/*
2 * Copyright 2004-2022 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/msg_xml.h>
37#include <crm/cib/internal.h>
38#include <crm/common/xml.h>
39#include <crm/common/util.h>
40#include <crm/common/ipc.h>
41#include <crm/common/iso8601.h>
42#include <crm/common/mainloop.h>
43#include <libxml2/libxml/relaxng.h>
44
45#include "crmcommon_private.h"
46
48
49gboolean crm_config_error = FALSE;
50gboolean crm_config_warning = FALSE;
51char *crm_system_name = NULL;
52
53bool
54pcmk__is_user_in_group(const char *user, const char *group)
55{
56 struct group *grent;
57 char **gr_mem;
58
59 if (user == NULL || group == NULL) {
60 return false;
61 }
62
63 setgrent();
64 while ((grent = getgrent()) != NULL) {
65 if (grent->gr_mem == NULL) {
66 continue;
67 }
68
69 if(strcmp(group, grent->gr_name) != 0) {
70 continue;
71 }
72
73 gr_mem = grent->gr_mem;
74 while (*gr_mem != NULL) {
75 if (!strcmp(user, *gr_mem++)) {
76 endgrent();
77 return true;
78 }
79 }
80 }
81 endgrent();
82 return false;
83}
84
85int
86crm_user_lookup(const char *name, uid_t * uid, gid_t * gid)
87{
88 int rc = pcmk_ok;
89 char *buffer = NULL;
90 struct passwd pwd;
91 struct passwd *pwentry = NULL;
92
93 buffer = calloc(1, PCMK__PW_BUFFER_LEN);
94 if (buffer == NULL) {
95 return -ENOMEM;
96 }
97
98 rc = getpwnam_r(name, &pwd, buffer, PCMK__PW_BUFFER_LEN, &pwentry);
99 if (pwentry) {
100 if (uid) {
101 *uid = pwentry->pw_uid;
102 }
103 if (gid) {
104 *gid = pwentry->pw_gid;
105 }
106 crm_trace("User %s has uid=%d gid=%d", name, pwentry->pw_uid, pwentry->pw_gid);
107
108 } else {
109 rc = rc? -rc : -EINVAL;
110 crm_info("User %s lookup: %s", name, pcmk_strerror(rc));
111 }
112
113 free(buffer);
114 return rc;
115}
116
125int
126pcmk_daemon_user(uid_t *uid, gid_t *gid)
127{
128 static uid_t daemon_uid;
129 static gid_t daemon_gid;
130 static bool found = false;
131 int rc = pcmk_ok;
132
133 if (!found) {
134 rc = crm_user_lookup(CRM_DAEMON_USER, &daemon_uid, &daemon_gid);
135 if (rc == pcmk_ok) {
136 found = true;
137 }
138 }
139 if (found) {
140 if (uid) {
141 *uid = daemon_uid;
142 }
143 if (gid) {
144 *gid = daemon_gid;
145 }
146 }
147 return rc;
148}
149
157static int
158version_helper(const char *text, const char **end_text)
159{
160 int atoi_result = -1;
161
162 CRM_ASSERT(end_text != NULL);
163
164 errno = 0;
165
166 if (text != NULL && text[0] != 0) {
167 /* seemingly sacrificing const-correctness -- because while strtol
168 doesn't modify the input, it doesn't want to artificially taint the
169 "end_text" pointer-to-pointer-to-first-char-in-string with constness
170 in case the input wasn't actually constant -- by semantic definition
171 not a single character will get modified so it shall be perfectly
172 safe to make compiler happy with dropping "const" qualifier here */
173 atoi_result = (int) strtol(text, (char **) end_text, 10);
174
175 if (errno == EINVAL) {
176 crm_err("Conversion of '%s' %c failed", text, text[0]);
177 atoi_result = -1;
178 }
179 }
180 return atoi_result;
181}
182
183/*
184 * version1 < version2 : -1
185 * version1 = version2 : 0
186 * version1 > version2 : 1
187 */
188int
189compare_version(const char *version1, const char *version2)
190{
191 int rc = 0;
192 int lpc = 0;
193 const char *ver1_iter, *ver2_iter;
194
195 if (version1 == NULL && version2 == NULL) {
196 return 0;
197 } else if (version1 == NULL) {
198 return -1;
199 } else if (version2 == NULL) {
200 return 1;
201 }
202
203 ver1_iter = version1;
204 ver2_iter = version2;
205
206 while (1) {
207 int digit1 = 0;
208 int digit2 = 0;
209
210 lpc++;
211
212 if (ver1_iter == ver2_iter) {
213 break;
214 }
215
216 if (ver1_iter != NULL) {
217 digit1 = version_helper(ver1_iter, &ver1_iter);
218 }
219
220 if (ver2_iter != NULL) {
221 digit2 = version_helper(ver2_iter, &ver2_iter);
222 }
223
224 if (digit1 < digit2) {
225 rc = -1;
226 break;
227
228 } else if (digit1 > digit2) {
229 rc = 1;
230 break;
231 }
232
233 if (ver1_iter != NULL && *ver1_iter == '.') {
234 ver1_iter++;
235 }
236 if (ver1_iter != NULL && *ver1_iter == '\0') {
237 ver1_iter = NULL;
238 }
239
240 if (ver2_iter != NULL && *ver2_iter == '.') {
241 ver2_iter++;
242 }
243 if (ver2_iter != NULL && *ver2_iter == 0) {
244 ver2_iter = NULL;
245 }
246 }
247
248 if (rc == 0) {
249 crm_trace("%s == %s (%d)", version1, version2, lpc);
250 } else if (rc < 0) {
251 crm_trace("%s < %s (%d)", version1, version2, lpc);
252 } else if (rc > 0) {
253 crm_trace("%s > %s (%d)", version1, version2, lpc);
254 }
255
256 return rc;
257}
258
270guint
272{
273 long long msec = -1;
274
275 errno = 0;
276 if (input == NULL) {
277 return 0;
278
279 } else if (input[0] == 'P') {
281
282 if (period_s) {
283 msec = 1000 * crm_time_get_seconds(period_s);
284 crm_time_free(period_s);
285 }
286
287 } else {
288 msec = crm_get_msec(input);
289 }
290
291 if (msec < 0) {
292 crm_warn("Using 0 instead of '%s'", input);
293 errno = EINVAL;
294 return 0;
295 }
296 return (msec >= G_MAXUINT)? G_MAXUINT : (guint) msec;
297}
298
308static void
309log_assertion_as(const char *file, const char *function, int line,
310 const char *assert_condition)
311{
312 if (!pcmk__is_daemon) {
313 crm_enable_stderr(TRUE); // Make sure command-line user sees message
314 }
315 crm_err("%s: Triggered fatal assertion at %s:%d : %s",
316 function, file, line, assert_condition);
317}
318
319/* coverity[+kill] */
331static _Noreturn void
332abort_as(const char *file, const char *function, int line,
333 const char *assert_condition)
334{
335 log_assertion_as(file, function, line, assert_condition);
336 abort();
337}
338
339/* coverity[+kill] */
352static void
353fail_assert_as(const char *file, const char *function, int line,
354 const char *assert_condition)
355{
356 int status = 0;
357 pid_t pid = 0;
358
359 if (!pcmk__is_daemon) {
360 abort_as(file, function, line, assert_condition); // does not return
361 }
362
363 pid = fork();
364 switch (pid) {
365 case -1: // Fork failed
366 crm_warn("%s: Cannot dump core for non-fatal assertion at %s:%d "
367 ": %s", function, file, line, assert_condition);
368 break;
369
370 case 0: // Child process: just abort to dump core
371 abort();
372 break;
373
374 default: // Parent process: wait for child
375 crm_err("%s: Forked child [%d] to record non-fatal assertion at "
376 "%s:%d : %s", function, pid, file, line, assert_condition);
377 crm_write_blackbox(SIGTRAP, NULL);
378 do {
379 if (waitpid(pid, &status, 0) == pid) {
380 return; // Child finished dumping core
381 }
382 } while (errno == EINTR);
383 if (errno == ECHILD) {
384 // crm_mon ignores SIGCHLD
385 crm_trace("Cannot wait on forked child [%d] "
386 "(SIGCHLD is probably ignored)", pid);
387 } else {
388 crm_err("Cannot wait on forked child [%d]: %s",
389 pid, pcmk_rc_str(errno));
390 }
391 break;
392 }
393}
394
395/* coverity[+kill] */
396void
397crm_abort(const char *file, const char *function, int line,
398 const char *assert_condition, gboolean do_core, gboolean do_fork)
399{
400 if (!do_fork) {
401 abort_as(file, function, line, assert_condition);
402 } else if (do_core) {
403 fail_assert_as(file, function, line, assert_condition);
404 } else {
405 log_assertion_as(file, function, line, assert_condition);
406 }
407}
408
420void
421pcmk__daemonize(const char *name, const char *pidfile)
422{
423 int rc;
424 pid_t pid;
425
426 /* Check before we even try... */
427 rc = pcmk__pidfile_matches(pidfile, 1, name, &pid);
428 if ((rc != pcmk_rc_ok) && (rc != ENOENT)) {
429 crm_err("%s: already running [pid %lld in %s]",
430 name, (long long) pid, pidfile);
431 printf("%s: already running [pid %lld in %s]\n",
432 name, (long long) pid, pidfile);
434 }
435
436 pid = fork();
437 if (pid < 0) {
438 fprintf(stderr, "%s: could not start daemon\n", name);
439 crm_perror(LOG_ERR, "fork");
441
442 } else if (pid > 0) {
444 }
445
446 rc = pcmk__lock_pidfile(pidfile, name);
447 if (rc != pcmk_rc_ok) {
448 crm_err("Could not lock '%s' for %s: %s " CRM_XS " rc=%d",
449 pidfile, name, pcmk_rc_str(rc), rc);
450 printf("Could not lock '%s' for %s: %s (%d)\n",
451 pidfile, name, pcmk_rc_str(rc), rc);
453 }
454
455 umask(S_IWGRP | S_IWOTH | S_IROTH);
456
457 close(STDIN_FILENO);
458 pcmk__open_devnull(O_RDONLY); // stdin (fd 0)
459
460 close(STDOUT_FILENO);
461 pcmk__open_devnull(O_WRONLY); // stdout (fd 1)
462
463 close(STDERR_FILENO);
464 pcmk__open_devnull(O_WRONLY); // stderr (fd 2)
465}
466
467char *
468crm_meta_name(const char *field)
469{
470 int lpc = 0;
471 int max = 0;
472 char *crm_name = NULL;
473
474 CRM_CHECK(field != NULL, return NULL);
475 crm_name = crm_strdup_printf(CRM_META "_%s", field);
476
477 /* Massage the names so they can be used as shell variables */
478 max = strlen(crm_name);
479 for (; lpc < max; lpc++) {
480 switch (crm_name[lpc]) {
481 case '-':
482 crm_name[lpc] = '_';
483 break;
484 }
485 }
486 return crm_name;
487}
488
489const char *
490crm_meta_value(GHashTable * hash, const char *field)
491{
492 char *key = NULL;
493 const char *value = NULL;
494
495 key = crm_meta_name(field);
496 if (key) {
497 value = g_hash_table_lookup(hash, key);
498 free(key);
499 }
500
501 return value;
502}
503
504#ifdef HAVE_UUID_UUID_H
505# include <uuid/uuid.h>
506#endif
507
508char *
510{
511 unsigned char uuid[16];
512 char *buffer = malloc(37); /* Including NUL byte */
513
514 CRM_ASSERT(buffer != NULL);
515 uuid_generate(uuid);
516 uuid_unparse(uuid, buffer);
517 return buffer;
518}
519
520#ifdef HAVE_GNUTLS_GNUTLS_H
521void
522crm_gnutls_global_init(void)
523{
524 signal(SIGPIPE, SIG_IGN);
525 gnutls_global_init();
526}
527#endif
528
534char *
536{
537 struct utsname hostinfo;
538
539 return (uname(&hostinfo) < 0)? NULL : strdup(hostinfo.nodename);
540}
541
542bool
543pcmk_str_is_infinity(const char *s) {
545}
546
547bool
549 return pcmk__str_eq(s, CRM_MINUS_INFINITY_S, pcmk__str_none);
550}
551
560void
561pcmk__sleep_ms(unsigned int ms)
562{
563 // @TODO Impose a sane maximum sleep to avoid hanging a process for long
564 //CRM_CHECK(ms <= MAX_SLEEP, ms = MAX_SLEEP);
565
566 // Use sleep() for any whole seconds
567 if (ms >= 1000) {
568 sleep(ms / 1000);
569 ms -= ms / 1000;
570 }
571
572 if (ms == 0) {
573 return;
574 }
575
576#if defined(HAVE_NANOSLEEP)
577 // nanosleep() is POSIX-2008, so prefer that
578 {
579 struct timespec req = { .tv_sec = 0, .tv_nsec = (long) (ms * 1000000) };
580
581 nanosleep(&req, NULL);
582 }
583#elif defined(HAVE_USLEEP)
584 // usleep() is widely available, though considered obsolete
585 usleep((useconds_t) ms);
586#else
587 // Otherwise use a trick with select() timeout
588 {
589 struct timeval tv = { .tv_sec = 0, .tv_usec = (suseconds_t) ms };
590
591 select(0, NULL, NULL, NULL, &tv);
592 }
593#endif
594}
const char * name
Definition: cib.c:24
int pcmk__pidfile_matches(const char *filename, pid_t expected_pid, const char *expected_name, pid_t *pid)
Definition: pid.c:165
int pcmk__lock_pidfile(const char *filename, const char *name)
Definition: pid.c:207
Utility functions.
long long crm_get_msec(const char *input)
Parse a time+units string and return milliseconds equivalent.
Definition: strings.c:364
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
const char * crm_meta_value(GHashTable *hash, const char *field)
Definition: utils.c:490
bool pcmk_str_is_infinity(const char *s)
Definition: utils.c:543
int pcmk_daemon_user(uid_t *uid, gid_t *gid)
Get user and group IDs of pacemaker daemon user.
Definition: utils.c:126
gboolean crm_config_warning
Definition: utils.c:50
char * crm_generate_uuid(void)
Definition: utils.c:509
CRM_TRACE_INIT_DATA(common)
char * crm_meta_name(const char *field)
Definition: utils.c:468
void pcmk__daemonize(const char *name, const char *pidfile)
Definition: utils.c:421
bool pcmk__is_user_in_group(const char *user, const char *group)
Definition: utils.c:54
int crm_user_lookup(const char *name, uid_t *uid, gid_t *gid)
Definition: utils.c:86
guint crm_parse_interval_spec(const char *input)
Parse milliseconds from a Pacemaker interval specification.
Definition: utils.c:271
int compare_version(const char *version1, const char *version2)
Definition: utils.c:189
void crm_abort(const char *file, const char *function, int line, const char *assert_condition, gboolean do_core, gboolean do_fork)
Definition: utils.c:397
char * pcmk_hostname(void)
Get the local hostname.
Definition: utils.c:535
void pcmk__sleep_ms(unsigned int ms)
Definition: utils.c:561
gboolean crm_config_error
Definition: utils.c:49
char * crm_system_name
Definition: utils.c:51
bool pcmk_str_is_minus_infinity(const char *s)
Definition: utils.c:548
#define CRM_DAEMON_USER
Definition: config.h:30
#define _Noreturn
Definition: config.h:648
char uname[MAX_NAME]
Definition: cpg.c:5
uint32_t pid
Definition: cpg.c:1
A dumping ground.
#define CRM_PLUS_INFINITY_S
Definition: crm.h:87
#define CRM_META
Definition: crm.h:78
#define CRM_INFINITY_S
Definition: crm.h:86
#define CRM_MINUS_INFINITY_S
Definition: crm.h:88
#define PCMK__PW_BUFFER_LEN
IPC interface to Pacemaker daemons.
ISO_8601 Date handling.
crm_time_t * crm_time_parse_duration(const char *duration_str)
Parse a time duration from an ISO 8601 duration specification.
Definition: iso8601.c:986
void crm_time_free(crm_time_t *dt)
Definition: iso8601.c:140
long long crm_time_get_seconds(const crm_time_t *dt)
Definition: iso8601.c:309
struct crm_time_s crm_time_t
Definition: iso8601.h:32
#define crm_info(fmt, args...)
Definition: logging.h:362
void crm_write_blackbox(int nsig, const struct qb_log_callsite *callsite)
Definition: logging.c:474
#define crm_warn(fmt, args...)
Definition: logging.h:360
#define CRM_XS
Definition: logging.h:55
void crm_enable_stderr(int enable)
Definition: logging.c:998
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition: logging.h:310
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:227
#define crm_err(fmt, args...)
Definition: logging.h:359
#define crm_trace(fmt, args...)
Definition: logging.h:365
Wrappers for and extensions to glib mainloop.
xmlNode * input
bool pcmk__is_daemon
Definition: logging.c:47
const char * pcmk_strerror(int rc)
Definition: results.c:148
#define CRM_ASSERT(expr)
Definition: results.h:42
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
Definition: results.c:476
@ CRM_EX_ERROR
Unspecified error.
Definition: results.h:235
@ CRM_EX_OSERR
External (OS/environmental) problem.
Definition: results.h:255
@ CRM_EX_OK
Success.
Definition: results.h:234
_Noreturn crm_exit_t crm_exit(crm_exit_t rc)
Definition: results.c:856
@ pcmk_rc_ok
Definition: results.h:148
#define pcmk_ok
Definition: results.h:68
Services API.
@ pcmk__str_none
bool pcmk__str_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:952
Wrappers for and extensions to libxml2.