pacemaker  2.1.4-dc6eb4362
Scalable High-Availability cluster resource manager
options.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 #ifndef _GNU_SOURCE
11 # define _GNU_SOURCE
12 #endif
13 
14 #include <crm_internal.h>
15 
16 #include <stdio.h>
17 #include <string.h>
18 #include <stdlib.h>
19 #include <sys/types.h>
20 #include <sys/stat.h>
21 
22 #ifdef HAVE_GETOPT_H
23 # include <getopt.h>
24 #endif
25 
26 #include <crm/crm.h>
27 
28 
29 /*
30  * Command-line option handling
31  */
32 
33 static char *crm_short_options = NULL;
34 static pcmk__cli_option_t *crm_long_options = NULL;
35 static const char *crm_app_description = NULL;
36 static const char *crm_app_usage = NULL;
37 
38 void
40 {
41  free(crm_short_options);
42  crm_short_options = NULL;
43 }
44 
45 static struct option *
46 create_long_opts(pcmk__cli_option_t *long_options)
47 {
48  struct option *long_opts = NULL;
49 
50 #ifdef HAVE_GETOPT_H
51  int index = 0, lpc = 0;
52 
53  /*
54  * A previous, possibly poor, choice of '?' as the short form of --help
55  * means that getopt_long() returns '?' for both --help and for "unknown option"
56  *
57  * This dummy entry allows us to differentiate between the two in
58  * pcmk__next_cli_option() and exit with the correct error code.
59  */
60  long_opts = pcmk__realloc(long_opts, (index + 1) * sizeof(struct option));
61  long_opts[index].name = "__dummmy__";
62  long_opts[index].has_arg = 0;
63  long_opts[index].flag = 0;
64  long_opts[index].val = '_';
65  index++;
66 
67  // cppcheck seems not to understand the abort-logic in pcmk__realloc
68  // cppcheck-suppress memleak
69  for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
70  if (long_options[lpc].name[0] == '-') {
71  continue;
72  }
73 
74  long_opts = pcmk__realloc(long_opts, (index + 1) * sizeof(struct option));
75  /*fprintf(stderr, "Creating %d %s = %c\n", index,
76  * long_options[lpc].name, long_options[lpc].val); */
77  long_opts[index].name = long_options[lpc].name;
78  long_opts[index].has_arg = long_options[lpc].has_arg;
79  long_opts[index].flag = long_options[lpc].flag;
80  long_opts[index].val = long_options[lpc].val;
81  index++;
82  }
83 
84  /* Now create the list terminator */
85  long_opts = pcmk__realloc(long_opts, (index + 1) * sizeof(struct option));
86  long_opts[index].name = NULL;
87  long_opts[index].has_arg = 0;
88  long_opts[index].flag = 0;
89  long_opts[index].val = 0;
90 #endif
91 
92  return long_opts;
93 }
94 
104 void
105 pcmk__set_cli_options(const char *short_options, const char *app_usage,
106  pcmk__cli_option_t *long_options, const char *app_desc)
107 {
108  if (short_options) {
109  crm_short_options = strdup(short_options);
110 
111  } else if (long_options) {
112  int lpc = 0;
113  int opt_string_len = 0;
114  char *local_short_options = NULL;
115 
116  for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
117  if (long_options[lpc].val && long_options[lpc].val != '-' && long_options[lpc].val < UCHAR_MAX) {
118  local_short_options = pcmk__realloc(local_short_options,
119  opt_string_len + 4);
120  local_short_options[opt_string_len++] = long_options[lpc].val;
121  /* getopt(3) says: Two colons mean an option takes an optional arg; */
122  if (long_options[lpc].has_arg == optional_argument) {
123  local_short_options[opt_string_len++] = ':';
124  }
125  if (long_options[lpc].has_arg >= required_argument) {
126  local_short_options[opt_string_len++] = ':';
127  }
128  local_short_options[opt_string_len] = 0;
129  }
130  }
131  crm_short_options = local_short_options;
132  crm_trace("Generated short option string: '%s'", local_short_options);
133  }
134 
135  if (long_options) {
136  crm_long_options = long_options;
137  }
138  if (app_desc) {
139  crm_app_description = app_desc;
140  }
141  if (app_usage) {
142  crm_app_usage = app_usage;
143  }
144 }
145 
146 int
147 pcmk__next_cli_option(int argc, char **argv, int *index, const char **longname)
148 {
149 #ifdef HAVE_GETOPT_H
150  static struct option *long_opts = NULL;
151 
152  if (long_opts == NULL && crm_long_options) {
153  long_opts = create_long_opts(crm_long_options);
154  }
155 
156  *index = 0;
157  if (long_opts) {
158  int flag = getopt_long(argc, argv, crm_short_options, long_opts, index);
159 
160  switch (flag) {
161  case 0:
162  if (long_opts[*index].val) {
163  return long_opts[*index].val;
164  } else if (longname) {
165  *longname = long_opts[*index].name;
166  } else {
167  crm_notice("Unhandled option --%s", long_opts[*index].name);
168  return flag;
169  }
170  case -1: /* End of option processing */
171  break;
172  case ':':
173  crm_trace("Missing argument");
175  break;
176  case '?':
177  pcmk__cli_help('?', (*index? CRM_EX_OK : CRM_EX_USAGE));
178  break;
179  }
180  return flag;
181  }
182 #endif
183 
184  if (crm_short_options) {
185  return getopt(argc, argv, crm_short_options);
186  }
187 
188  return -1;
189 }
190 
191 void
192 pcmk__cli_help(char cmd, crm_exit_t exit_code)
193 {
194  int i = 0;
195  FILE *stream = (exit_code ? stderr : stdout);
196 
197  if (cmd == 'v' || cmd == '$') {
198  fprintf(stream, "Pacemaker %s\n", PACEMAKER_VERSION);
199  fprintf(stream, "Written by Andrew Beekhof and "
200  "the Pacemaker project contributors\n");
201  goto out;
202  }
203 
204  if (cmd == '!') {
205  fprintf(stream, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
206  goto out;
207  }
208 
209  fprintf(stream, "%s - %s\n", crm_system_name, crm_app_description);
210 
211  if (crm_app_usage) {
212  fprintf(stream, "Usage: %s %s\n", crm_system_name, crm_app_usage);
213  }
214 
215  if (crm_long_options) {
216  fprintf(stream, "Options:\n");
217  for (i = 0; crm_long_options[i].name != NULL; i++) {
218  if (crm_long_options[i].flags & pcmk__option_hidden) {
219 
220  } else if (crm_long_options[i].flags & pcmk__option_paragraph) {
221  fprintf(stream, "%s\n\n", crm_long_options[i].desc);
222 
223  } else if (crm_long_options[i].flags & pcmk__option_example) {
224  fprintf(stream, "\t#%s\n\n", crm_long_options[i].desc);
225 
226  } else if (crm_long_options[i].val == '-' && crm_long_options[i].desc) {
227  fprintf(stream, "%s\n", crm_long_options[i].desc);
228 
229  } else {
230  /* is val printable as char ? */
231  if (crm_long_options[i].val && crm_long_options[i].val <= UCHAR_MAX) {
232  fprintf(stream, " -%c,", crm_long_options[i].val);
233  } else {
234  fputs(" ", stream);
235  }
236  fprintf(stream, " --%s%s\t%s\n", crm_long_options[i].name,
237  crm_long_options[i].has_arg == optional_argument ? "[=value]" :
238  crm_long_options[i].has_arg == required_argument ? "=value" : "",
239  crm_long_options[i].desc ? crm_long_options[i].desc : "");
240  }
241  }
242 
243  } else if (crm_short_options) {
244  fprintf(stream, "Usage: %s - %s\n", crm_system_name, crm_app_description);
245  for (i = 0; crm_short_options[i] != 0; i++) {
246  int has_arg = no_argument /* 0 */;
247 
248  if (crm_short_options[i + 1] == ':') {
249  if (crm_short_options[i + 2] == ':')
250  has_arg = optional_argument /* 2 */;
251  else
252  has_arg = required_argument /* 1 */;
253  }
254 
255  fprintf(stream, " -%c %s\n", crm_short_options[i],
256  has_arg == optional_argument ? "[value]" :
257  has_arg == required_argument ? "{value}" : "");
258  i += has_arg;
259  }
260  }
261 
262  fprintf(stream, "\nReport bugs to %s\n", PACKAGE_BUGREPORT);
263 
264  out:
265  crm_exit(exit_code);
266  while(1); // above does not return
267 }
268 
269 
270 /*
271  * Environment variable option handling
272  */
273 
285 const char *
286 pcmk__env_option(const char *option)
287 {
288  char env_name[NAME_MAX];
289  const char *value = NULL;
290 
291  snprintf(env_name, NAME_MAX, "PCMK_%s", option);
292  value = getenv(env_name);
293  if (value != NULL) {
294  crm_trace("Found %s = %s", env_name, value);
295  return value;
296  }
297 
298  snprintf(env_name, NAME_MAX, "HA_%s", option);
299  value = getenv(env_name);
300  if (value != NULL) {
301  crm_trace("Found %s = %s", env_name, value);
302  return value;
303  }
304 
305  crm_trace("Nothing found for %s", option);
306  return NULL;
307 }
308 
318 void
319 pcmk__set_env_option(const char *option, const char *value)
320 {
321  char env_name[NAME_MAX];
322 
323  snprintf(env_name, NAME_MAX, "PCMK_%s", option);
324  if (value) {
325  crm_trace("Setting %s to %s", env_name, value);
326  setenv(env_name, value, 1);
327  } else {
328  crm_trace("Unsetting %s", env_name);
329  unsetenv(env_name);
330  }
331 
332  snprintf(env_name, NAME_MAX, "HA_%s", option);
333  if (value) {
334  crm_trace("Setting %s to %s", env_name, value);
335  setenv(env_name, value, 1);
336  } else {
337  crm_trace("Unsetting %s", env_name);
338  unsetenv(env_name);
339  }
340 }
341 
355 bool
356 pcmk__env_option_enabled(const char *daemon, const char *option)
357 {
358  const char *value = pcmk__env_option(option);
359 
360  return (value != NULL) && (crm_is_true(value) || strstr(value, daemon));
361 }
362 
363 
364 /*
365  * Cluster option handling
366  */
367 
368 bool
369 pcmk__valid_interval_spec(const char *value)
370 {
371  (void) crm_parse_interval_spec(value);
372  return errno == 0;
373 }
374 
375 bool
376 pcmk__valid_boolean(const char *value)
377 {
378  int tmp;
379 
380  return crm_str_to_boolean(value, &tmp) == 1;
381 }
382 
383 bool
384 pcmk__valid_number(const char *value)
385 {
386  if (value == NULL) {
387  return false;
388 
389  } else if (pcmk_str_is_minus_infinity(value) ||
390  pcmk_str_is_infinity(value)) {
391  return true;
392  }
393 
394  return pcmk__scan_ll(value, NULL, 0LL) == pcmk_rc_ok;
395 }
396 
397 bool
398 pcmk__valid_positive_number(const char *value)
399 {
400  long long num = 0LL;
401 
402  return pcmk_str_is_infinity(value)
403  || ((pcmk__scan_ll(value, &num, 0LL) == pcmk_rc_ok) && (num > 0));
404 }
405 
406 bool
407 pcmk__valid_quorum(const char *value)
408 {
409  return pcmk__strcase_any_of(value, "stop", "freeze", "ignore", "demote", "suicide", NULL);
410 }
411 
412 bool
413 pcmk__valid_script(const char *value)
414 {
415  struct stat st;
416 
417  if (pcmk__str_eq(value, "/dev/null", pcmk__str_casei)) {
418  return true;
419  }
420 
421  if (stat(value, &st) != 0) {
422  crm_err("Script %s does not exist", value);
423  return false;
424  }
425 
426  if (S_ISREG(st.st_mode) == 0) {
427  crm_err("Script %s is not a regular file", value);
428  return false;
429  }
430 
431  if ((st.st_mode & (S_IXUSR | S_IXGRP)) == 0) {
432  crm_err("Script %s is not executable", value);
433  return false;
434  }
435 
436  return true;
437 }
438 
439 bool
440 pcmk__valid_percentage(const char *value)
441 {
442  char *end = NULL;
443  long number = strtol(value, &end, 10);
444 
445  if (end && (end[0] != '%')) {
446  return false;
447  }
448  return number >= 0;
449 }
450 
463 static const char *
464 cluster_option_value(GHashTable *options, bool (*validate)(const char *),
465  const char *name, const char *old_name,
466  const char *def_value)
467 {
468  const char *value = NULL;
469  char *new_value = NULL;
470 
471  CRM_ASSERT(name != NULL);
472 
473  if (options) {
474  value = g_hash_table_lookup(options, name);
475 
476  if ((value == NULL) && old_name) {
477  value = g_hash_table_lookup(options, old_name);
478  if (value != NULL) {
479  pcmk__config_warn("Support for legacy name '%s' for cluster "
480  "option '%s' is deprecated and will be "
481  "removed in a future release",
482  old_name, name);
483 
484  // Inserting copy with current name ensures we only warn once
485  new_value = strdup(value);
486  g_hash_table_insert(options, strdup(name), new_value);
487  value = new_value;
488  }
489  }
490 
491  if (value && validate && (validate(value) == FALSE)) {
492  pcmk__config_err("Using default value for cluster option '%s' "
493  "because '%s' is invalid", name, value);
494  value = NULL;
495  }
496 
497  if (value) {
498  return value;
499  }
500  }
501 
502  // No value found, use default
503  value = def_value;
504 
505  if (value == NULL) {
506  crm_trace("No value or default provided for cluster option '%s'",
507  name);
508  return NULL;
509  }
510 
511  if (validate) {
512  CRM_CHECK(validate(value) != FALSE,
513  crm_err("Bug: default value for cluster option '%s' is invalid", name);
514  return NULL);
515  }
516 
517  crm_trace("Using default value '%s' for cluster option '%s'",
518  value, name);
519  if (options) {
520  new_value = strdup(value);
521  g_hash_table_insert(options, strdup(name), new_value);
522  value = new_value;
523  }
524  return value;
525 }
526 
537 const char *
538 pcmk__cluster_option(GHashTable *options, pcmk__cluster_option_t *option_list,
539  int len, const char *name)
540 {
541  const char *value = NULL;
542 
543  for (int lpc = 0; lpc < len; lpc++) {
544  if (pcmk__str_eq(name, option_list[lpc].name, pcmk__str_casei)) {
545  value = cluster_option_value(options, option_list[lpc].is_valid,
546  option_list[lpc].name,
547  option_list[lpc].alt_name,
548  option_list[lpc].default_value);
549  return value;
550  }
551  }
552  CRM_CHECK(FALSE, crm_err("Bug: looking for unknown option '%s'", name));
553  return NULL;
554 }
555 
565 static void
566 add_desc(GString *s, const char *tag, const char *desc, const char *values, const char *spaces)
567 {
568  char *escaped_en = crm_xml_escape(desc);
569 
570  g_string_append_printf(s, "<%s lang=\"en\">%s",
571  tag, escaped_en);
572  if (values != NULL) {
573  g_string_append_printf(s, " Allowed values: %s", values);
574  }
575  g_string_append_printf(s, "</%s>\n", tag);
576 
577 #ifdef ENABLE_NLS
578  {
579  static const char *locale = NULL;
580 
581  char *localized = crm_xml_escape(_(desc));
582 
583  if (strcmp(escaped_en, localized) != 0) {
584  if (locale == NULL) {
585  locale = strtok(setlocale(LC_ALL, NULL), "_");
586  }
587 
588  if (spaces != NULL) {
589  g_string_append_printf(s, "%s", spaces);
590  }
591  g_string_append_printf(s, "<%s lang=\"%s\">%s",
592  tag, locale, localized);
593  if (values != NULL) {
594  g_string_append(s, _(" Allowed values: "));
595  g_string_append_printf(s, "%s", _(values));
596  }
597  g_string_append_printf(s, "</%s>\n", tag);
598  }
599  free(localized);
600  }
601 #endif
602 
603  free(escaped_en);
604 }
605 
606 char *
607 pcmk__format_option_metadata(const char *name, const char *desc_short,
608  const char *desc_long,
609  pcmk__cluster_option_t *option_list, int len)
610 {
611  char *retval;
612  /* big enough to hold "pacemaker-schedulerd metadata" output */
613  GString *s = g_string_sized_new(13000);
614  int lpc = 0;
615 
616  g_string_append_printf(s, "<?xml version=\"1.0\"?>"
617  "<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n"
618  "<resource-agent name=\"%s\">\n"
619  " <version>%s</version>\n",
621 
622  g_string_append(s, " ");
623  add_desc(s, "longdesc", desc_long, NULL, " ");
624 
625  g_string_append(s, " ");
626  add_desc(s, "shortdesc", desc_short, NULL, " ");
627 
628  g_string_append(s, " <parameters>\n");
629 
630  for (lpc = 0; lpc < len; lpc++) {
631  const char *long_desc = option_list[lpc].description_long;
632 
633  if (long_desc == NULL) {
634  long_desc = option_list[lpc].description_short;
635  if (long_desc == NULL) {
636  continue; // The standard requires a parameter description
637  }
638  }
639 
640  g_string_append_printf(s, " <parameter name=\"%s\">\n",
641  option_list[lpc].name);
642 
643  g_string_append(s, " ");
644  add_desc(s, "longdesc", long_desc, option_list[lpc].values, " ");
645 
646  g_string_append(s, " ");
647  add_desc(s, "shortdesc", option_list[lpc].description_short, NULL, " ");
648 
649  if (option_list[lpc].values && !strcmp(option_list[lpc].type, "select")) {
650  char *str = strdup(option_list[lpc].values);
651  char delim[] = ", ";
652  char *ptr = strtok(str, delim);
653 
654  g_string_append_printf(s, " <content type=\"%s\" default=\"%s\">\n",
655  option_list[lpc].type,
656  option_list[lpc].default_value);
657 
658  while (ptr != NULL) {
659  g_string_append_printf(s, " <option value=\"%s\" />\n", ptr);
660  ptr = strtok(NULL, delim);
661  }
662 
663  g_string_append_printf(s, " </content>\n");
664  free(str);
665 
666  } else {
667  g_string_append_printf(s, " <content type=\"%s\" default=\"%s\"/>\n",
668  option_list[lpc].type,
669  option_list[lpc].default_value
670  );
671  }
672 
673  g_string_append_printf(s, " </parameter>\n");
674  }
675  g_string_append_printf(s, " </parameters>\n</resource-agent>\n");
676 
677  retval = s->str;
678  g_string_free(s, FALSE);
679  return retval;
680 }
681 
682 void
683 pcmk__validate_cluster_options(GHashTable *options,
684  pcmk__cluster_option_t *option_list, int len)
685 {
686  for (int lpc = 0; lpc < len; lpc++) {
687  cluster_option_value(options, option_list[lpc].is_valid,
688  option_list[lpc].name,
689  option_list[lpc].alt_name,
690  option_list[lpc].default_value);
691  }
692 }
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:226
A dumping ground.
#define crm_notice(fmt, args...)
Definition: logging.h:360
void pcmk__set_env_option(const char *option, const char *value)
Set or unset a Pacemaker environment variable option.
Definition: options.c:319
_Noreturn crm_exit_t crm_exit(crm_exit_t rc)
Definition: results.c:775
void pcmk__cli_help(char cmd, crm_exit_t exit_code)
Definition: options.c:192
const char * name
Definition: cib.c:24
bool pcmk__strcase_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:931
bool pcmk__valid_script(const char *value)
Definition: options.c:413
#define pcmk__config_warn(fmt...)
bool pcmk__valid_interval_spec(const char *value)
Definition: options.c:369
#define pcmk__config_err(fmt...)
bool pcmk__valid_positive_number(const char *value)
Definition: options.c:398
char * crm_system_name
Definition: utils.c:54
#define required_argument
enum crm_exit_e crm_exit_t
Command line usage error.
Definition: results.h:251
enum crm_ais_msg_types type
Definition: cpg.c:48
#define PACEMAKER_VERSION
Definition: config.h:504
bool pcmk__valid_percentage(const char *value)
Definition: options.c:440
void pcmk__set_cli_options(const char *short_options, const char *app_usage, pcmk__cli_option_t *long_options, const char *app_desc)
Definition: options.c:105
int pcmk__scan_ll(const char *text, long long *result, long long default_value)
Definition: strings.c:97
int daemon(int nochdir, int noclose)
stonith_t * st
Definition: pcmk_fence.c:27
#define BUILD_VERSION
Definition: config.h:8
bool pcmk__valid_quorum(const char *value)
Definition: options.c:407
#define crm_trace(fmt, args...)
Definition: logging.h:364
int setenv(const char *name, const char *value, int why)
char * pcmk__format_option_metadata(const char *name, const char *desc_short, const char *desc_long, pcmk__cluster_option_t *option_list, int len)
Definition: options.c:607
#define _(String)
Definition: crm_internal.h:53
Success.
Definition: results.h:239
bool pcmk_str_is_infinity(const char *s)
Definition: utils.c:513
int crm_str_to_boolean(const char *s, int *ret)
Definition: strings.c:427
#define crm_err(fmt, args...)
Definition: logging.h:358
#define CRM_ASSERT(expr)
Definition: results.h:42
bool pcmk_str_is_minus_infinity(const char *s)
Definition: utils.c:518
char guint crm_parse_interval_spec(const char *input)
Parse milliseconds from a Pacemaker interval specification.
Definition: utils.c:242
int pcmk__next_cli_option(int argc, char **argv, int *index, const char **longname)
Definition: options.c:147
#define NAME_MAX
Definition: logging.c:103
#define no_argument
const char * description_short
#define PCMK_OCF_VERSION
Definition: agents.h:42
void pcmk__validate_cluster_options(GHashTable *options, pcmk__cluster_option_t *option_list, int len)
Definition: options.c:683
const char * pcmk__cluster_option(GHashTable *options, pcmk__cluster_option_t *option_list, int len, const char *name)
Definition: options.c:538
#define PACKAGE_BUGREPORT
Definition: config.h:510
bool pcmk__env_option_enabled(const char *daemon, const char *option)
Definition: options.c:356
gboolean crm_is_true(const char *s)
Definition: strings.c:416
const char * pcmk__env_option(const char *option)
Definition: options.c:286
bool pcmk__valid_number(const char *value)
Definition: options.c:384
char * crm_xml_escape(const char *text)
Replace special characters with their XML escape sequences.
Definition: xml.c:1345
#define CRM_FEATURES
Definition: config.h:33
bool pcmk__valid_boolean(const char *value)
Definition: options.c:376
uint64_t flags
Definition: remote.c:149
void pcmk__cli_option_cleanup()
Definition: options.c:39
const char * description_long