pacemaker  2.1.7-0f7f88312f
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 
47 CRM_TRACE_INIT_DATA(common);
48 
49 gboolean crm_config_error = FALSE;
50 gboolean crm_config_warning = FALSE;
51 char *crm_system_name = NULL;
52 
53 bool
54 pcmk__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 
85 int
86 crm_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 
125 int
126 pcmk_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 
157 static int
158 version_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  */
188 int
189 compare_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 
270 guint
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 
308 static void
309 log_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] */
331 static _Noreturn void
332 abort_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] */
352 static void
353 fail_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] */
396 void
397 crm_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 
420 void
421 pcmk__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 
467 char *
468 crm_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 
489 const char *
490 crm_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 
508 char *
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
521 void
522 crm_gnutls_global_init(void)
523 {
524  signal(SIGPIPE, SIG_IGN);
525  gnutls_global_init();
526 }
527 #endif
528 
534 char *
536 {
537  struct utsname hostinfo;
538 
539  return (uname(&hostinfo) < 0)? NULL : strdup(hostinfo.nodename);
540 }
541 
542 bool
543 pcmk_str_is_infinity(const char *s) {
545 }
546 
547 bool
549  return pcmk__str_eq(s, CRM_MINUS_INFINITY_S, pcmk__str_none);
550 }
551 
560 void
561 pcmk__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 }
Services API.
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:238
A dumping ground.
int crm_user_lookup(const char *name, uid_t *uid, gid_t *gid)
Definition: utils.c:86
const char * pcmk_strerror(int rc)
Definition: results.c:149
void crm_enable_stderr(int enable)
Definition: logging.c:1044
const char * crm_meta_value(GHashTable *hash, const char *field)
Definition: utils.c:490
_Noreturn crm_exit_t crm_exit(crm_exit_t rc)
Definition: results.c:936
const char * name
Definition: cib.c:26
bool pcmk_str_is_minus_infinity(const char *s)
Definition: utils.c:548
struct crm_time_s crm_time_t
Definition: iso8601.h:32
long long crm_get_msec(const char *input)
Parse a time+units string and return milliseconds equivalent.
Definition: strings.c:364
gboolean crm_config_warning
Definition: utils.c:50
Unspecified error.
Definition: results.h:241
bool pcmk_str_is_infinity(const char *s)
Definition: utils.c:543
crm_time_t * crm_time_parse_duration(const char *duration_str)
Parse a time duration from an ISO 8601 duration specification.
Definition: iso8601.c:1095
void pcmk__daemonize(const char *name, const char *pidfile)
Definition: utils.c:421
char * crm_system_name
Definition: utils.c:51
const char * pcmk_rc_str(int rc)
Get a user-friendly description of a return code.
Definition: results.c:501
#define _Noreturn
Definition: config.h:655
Wrappers for and extensions to glib mainloop.
char * crm_meta_name(const char *field)
Definition: utils.c:468
#define crm_warn(fmt, args...)
Definition: logging.h:382
uint32_t pid
Definition: cpg.c:46
guint crm_parse_interval_spec(const char *input)
Parse milliseconds from a Pacemaker interval specification.
Definition: utils.c:271
Utility functions.
void pcmk__sleep_ms(unsigned int ms)
Definition: utils.c:561
External (OS/environmental) problem.
Definition: results.h:261
#define crm_trace(fmt, args...)
Definition: logging.h:387
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
#define CRM_MINUS_INFINITY_S
Definition: crm.h:87
Wrappers for and extensions to libxml2.
ISO_8601 Date handling.
#define CRM_DAEMON_USER
Definition: config.h:30
Success.
Definition: results.h:240
char * crm_generate_uuid(void)
Definition: utils.c:509
bool pcmk__str_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:957
long long crm_time_get_seconds(const crm_time_t *dt)
Definition: iso8601.c:316
CRM_TRACE_INIT_DATA(common)
char * pcmk_hostname(void)
Get the local hostname.
Definition: utils.c:535
void crm_write_blackbox(int nsig, const struct qb_log_callsite *callsite)
Definition: logging.c:505
int compare_version(const char *version1, const char *version2)
Definition: utils.c:189
#define CRM_XS
Definition: logging.h:56
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
int pcmk__lock_pidfile(const char *filename, const char *name)
Definition: pid.c:214
#define crm_perror(level, fmt, args...)
Send a system error message to both the log and stderr.
Definition: logging.h:323
#define CRM_META
Definition: crm.h:79
#define crm_err(fmt, args...)
Definition: logging.h:381
#define CRM_ASSERT(expr)
Definition: results.h:42
#define CRM_INFINITY_S
Definition: crm.h:85
xmlNode * input
char uname[MAX_NAME]
Definition: cpg.c:50
int pcmk__pidfile_matches(const char *filename, pid_t expected_pid, const char *expected_name, pid_t *pid)
Definition: pid.c:172
#define PCMK__PW_BUFFER_LEN
#define pcmk_ok
Definition: results.h:68
IPC interface to Pacemaker daemons.
bool pcmk__is_daemon
Definition: logging.c:47
#define CRM_PLUS_INFINITY_S
Definition: crm.h:86
gboolean crm_config_error
Definition: utils.c:49
bool pcmk__is_user_in_group(const char *user, const char *group)
Definition: utils.c:54
int pcmk_daemon_user(uid_t *uid, gid_t *gid)
Get user and group IDs of pacemaker daemon user.
Definition: utils.c:126
#define crm_info(fmt, args...)
Definition: logging.h:384
void crm_time_free(crm_time_t *dt)
Definition: iso8601.c:150