pacemaker 2.1.5-a3f44794f94
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
33static char *crm_short_options = NULL;
34static const pcmk__cli_option_t *crm_long_options = NULL;
35static const char *crm_app_description = NULL;
36static const char *crm_app_usage = NULL;
37
38void
40{
41 free(crm_short_options);
42 crm_short_options = NULL;
43}
44
45static struct option *
46create_long_opts(const 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
104void
105pcmk__set_cli_options(const char *short_options, const char *app_usage,
106 const pcmk__cli_option_t *long_options,
107 const char *app_desc)
108{
109 if (short_options) {
110 crm_short_options = strdup(short_options);
111
112 } else if (long_options) {
113 int lpc = 0;
114 int opt_string_len = 0;
115 char *local_short_options = NULL;
116
117 for (lpc = 0; long_options[lpc].name != NULL; lpc++) {
118 if (long_options[lpc].val && long_options[lpc].val != '-' && long_options[lpc].val < UCHAR_MAX) {
119 local_short_options = pcmk__realloc(local_short_options,
120 opt_string_len + 4);
121 local_short_options[opt_string_len++] = long_options[lpc].val;
122 /* getopt(3) says: Two colons mean an option takes an optional arg; */
123 if (long_options[lpc].has_arg == optional_argument) {
124 local_short_options[opt_string_len++] = ':';
125 }
126 if (long_options[lpc].has_arg >= required_argument) {
127 local_short_options[opt_string_len++] = ':';
128 }
129 local_short_options[opt_string_len] = 0;
130 }
131 }
132 crm_short_options = local_short_options;
133 crm_trace("Generated short option string: '%s'", local_short_options);
134 }
135
136 if (long_options) {
137 crm_long_options = long_options;
138 }
139 if (app_desc) {
140 crm_app_description = app_desc;
141 }
142 if (app_usage) {
143 crm_app_usage = app_usage;
144 }
145}
146
147int
148pcmk__next_cli_option(int argc, char **argv, int *index, const char **longname)
149{
150#ifdef HAVE_GETOPT_H
151 static struct option *long_opts = NULL;
152
153 if (long_opts == NULL && crm_long_options) {
154 long_opts = create_long_opts(crm_long_options);
155 }
156
157 *index = 0;
158 if (long_opts) {
159 int flag = getopt_long(argc, argv, crm_short_options, long_opts, index);
160
161 switch (flag) {
162 case 0:
163 if (long_opts[*index].val) {
164 return long_opts[*index].val;
165 } else if (longname) {
166 *longname = long_opts[*index].name;
167 } else {
168 crm_notice("Unhandled option --%s", long_opts[*index].name);
169 return flag;
170 }
171 break;
172
173 case -1: /* End of option processing */
174 break;
175 case ':':
176 crm_trace("Missing argument");
178 break;
179 case '?':
180 pcmk__cli_help('?', (*index? CRM_EX_OK : CRM_EX_USAGE));
181 break;
182 }
183 return flag;
184 }
185#endif
186
187 if (crm_short_options) {
188 return getopt(argc, argv, crm_short_options);
189 }
190
191 return -1;
192}
193
194void
195pcmk__cli_help(char cmd, crm_exit_t exit_code)
196{
197 int i = 0;
198 FILE *stream = (exit_code ? stderr : stdout);
199
200 if (cmd == 'v' || cmd == '$') {
201 fprintf(stream, "Pacemaker %s\n", PACEMAKER_VERSION);
202 fprintf(stream, "Written by Andrew Beekhof and "
203 "the Pacemaker project contributors\n");
204 goto out;
205 }
206
207 if (cmd == '!') {
208 fprintf(stream, "Pacemaker %s (Build: %s): %s\n", PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES);
209 goto out;
210 }
211
212 fprintf(stream, "%s - %s\n", crm_system_name, crm_app_description);
213
214 if (crm_app_usage) {
215 fprintf(stream, "Usage: %s %s\n", crm_system_name, crm_app_usage);
216 }
217
218 if (crm_long_options) {
219 fprintf(stream, "Options:\n");
220 for (i = 0; crm_long_options[i].name != NULL; i++) {
221 if (crm_long_options[i].flags & pcmk__option_hidden) {
222
223 } else if (crm_long_options[i].flags & pcmk__option_paragraph) {
224 fprintf(stream, "%s\n\n", crm_long_options[i].desc);
225
226 } else if (crm_long_options[i].flags & pcmk__option_example) {
227 fprintf(stream, "\t#%s\n\n", crm_long_options[i].desc);
228
229 } else if (crm_long_options[i].val == '-' && crm_long_options[i].desc) {
230 fprintf(stream, "%s\n", crm_long_options[i].desc);
231
232 } else {
233 /* is val printable as char ? */
234 if (crm_long_options[i].val && crm_long_options[i].val <= UCHAR_MAX) {
235 fprintf(stream, " -%c,", crm_long_options[i].val);
236 } else {
237 fputs(" ", stream);
238 }
239 fprintf(stream, " --%s%s\t%s\n", crm_long_options[i].name,
240 crm_long_options[i].has_arg == optional_argument ? "[=value]" :
241 crm_long_options[i].has_arg == required_argument ? "=value" : "",
242 crm_long_options[i].desc ? crm_long_options[i].desc : "");
243 }
244 }
245
246 } else if (crm_short_options) {
247 fprintf(stream, "Usage: %s - %s\n", crm_system_name, crm_app_description);
248 for (i = 0; crm_short_options[i] != 0; i++) {
249 int has_arg = no_argument /* 0 */;
250
251 if (crm_short_options[i + 1] == ':') {
252 if (crm_short_options[i + 2] == ':')
253 has_arg = optional_argument /* 2 */;
254 else
255 has_arg = required_argument /* 1 */;
256 }
257
258 fprintf(stream, " -%c %s\n", crm_short_options[i],
259 has_arg == optional_argument ? "[value]" :
260 has_arg == required_argument ? "{value}" : "");
261 i += has_arg;
262 }
263 }
264
265 fprintf(stream, "\nReport bugs to %s\n", PACKAGE_BUGREPORT);
266
267 out:
268 crm_exit(exit_code);
269 while(1); // above does not return
270}
271
272
273/*
274 * Environment variable option handling
275 */
276
289const char *
290pcmk__env_option(const char *option)
291{
292 const char *const prefixes[] = {"PCMK_", "HA_"};
293 char env_name[NAME_MAX];
294 const char *value = NULL;
295
296 CRM_CHECK(!pcmk__str_empty(option), return NULL);
297
298 for (int i = 0; i < PCMK__NELEM(prefixes); i++) {
299 int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option);
300
301 if (rv < 0) {
302 crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option,
303 strerror(errno));
304 return NULL;
305 }
306
307 if (rv >= sizeof(env_name)) {
308 crm_trace("\"%s%s\" is too long", prefixes[i], option);
309 continue;
310 }
311
312 value = getenv(env_name);
313 if (value != NULL) {
314 crm_trace("Found %s = %s", env_name, value);
315 return value;
316 }
317 }
318
319 crm_trace("Nothing found for %s", option);
320 return NULL;
321}
322
332void
333pcmk__set_env_option(const char *option, const char *value)
334{
335 const char *const prefixes[] = {"PCMK_", "HA_"};
336 char env_name[NAME_MAX];
337
338 CRM_CHECK(!pcmk__str_empty(option) && (strchr(option, '=') == NULL),
339 return);
340
341 for (int i = 0; i < PCMK__NELEM(prefixes); i++) {
342 int rv = snprintf(env_name, NAME_MAX, "%s%s", prefixes[i], option);
343
344 if (rv < 0) {
345 crm_err("Failed to write %s%s to buffer: %s", prefixes[i], option,
346 strerror(errno));
347 return;
348 }
349
350 if (rv >= sizeof(env_name)) {
351 crm_trace("\"%s%s\" is too long", prefixes[i], option);
352 continue;
353 }
354
355 if (value != NULL) {
356 crm_trace("Setting %s to %s", env_name, value);
357 rv = setenv(env_name, value, 1);
358 } else {
359 crm_trace("Unsetting %s", env_name);
360 rv = unsetenv(env_name);
361 }
362
363 if (rv < 0) {
364 crm_err("Failed to %sset %s: %s", (value != NULL)? "" : "un",
365 env_name, strerror(errno));
366 }
367 }
368}
369
383bool
384pcmk__env_option_enabled(const char *daemon, const char *option)
385{
386 const char *value = pcmk__env_option(option);
387
388 return (value != NULL)
389 && (crm_is_true(value)
390 || ((daemon != NULL) && (strstr(value, daemon) != NULL)));
391}
392
393
394/*
395 * Cluster option handling
396 */
397
398bool
400{
401 (void) crm_parse_interval_spec(value);
402 return errno == 0;
403}
404
405bool
406pcmk__valid_boolean(const char *value)
407{
408 int tmp;
409
410 return crm_str_to_boolean(value, &tmp) == 1;
411}
412
413bool
414pcmk__valid_number(const char *value)
415{
416 if (value == NULL) {
417 return false;
418
419 } else if (pcmk_str_is_minus_infinity(value) ||
420 pcmk_str_is_infinity(value)) {
421 return true;
422 }
423
424 return pcmk__scan_ll(value, NULL, 0LL) == pcmk_rc_ok;
425}
426
427bool
429{
430 long long num = 0LL;
431
432 return pcmk_str_is_infinity(value)
433 || ((pcmk__scan_ll(value, &num, 0LL) == pcmk_rc_ok) && (num > 0));
434}
435
436bool
437pcmk__valid_quorum(const char *value)
438{
439 return pcmk__strcase_any_of(value, "stop", "freeze", "ignore", "demote", "suicide", NULL);
440}
441
442bool
443pcmk__valid_script(const char *value)
444{
445 struct stat st;
446
447 if (pcmk__str_eq(value, "/dev/null", pcmk__str_casei)) {
448 return true;
449 }
450
451 if (stat(value, &st) != 0) {
452 crm_err("Script %s does not exist", value);
453 return false;
454 }
455
456 if (S_ISREG(st.st_mode) == 0) {
457 crm_err("Script %s is not a regular file", value);
458 return false;
459 }
460
461 if ((st.st_mode & (S_IXUSR | S_IXGRP)) == 0) {
462 crm_err("Script %s is not executable", value);
463 return false;
464 }
465
466 return true;
467}
468
469bool
470pcmk__valid_percentage(const char *value)
471{
472 char *end = NULL;
473 long number = strtol(value, &end, 10);
474
475 if (end && (end[0] != '%')) {
476 return false;
477 }
478 return number >= 0;
479}
480
493static const char *
494cluster_option_value(GHashTable *options, bool (*validate)(const char *),
495 const char *name, const char *old_name,
496 const char *def_value)
497{
498 const char *value = NULL;
499 char *new_value = NULL;
500
501 CRM_ASSERT(name != NULL);
502
503 if (options) {
504 value = g_hash_table_lookup(options, name);
505
506 if ((value == NULL) && old_name) {
507 value = g_hash_table_lookup(options, old_name);
508 if (value != NULL) {
509 pcmk__config_warn("Support for legacy name '%s' for cluster "
510 "option '%s' is deprecated and will be "
511 "removed in a future release",
512 old_name, name);
513
514 // Inserting copy with current name ensures we only warn once
515 new_value = strdup(value);
516 g_hash_table_insert(options, strdup(name), new_value);
517 value = new_value;
518 }
519 }
520
521 if (value && validate && (validate(value) == FALSE)) {
522 pcmk__config_err("Using default value for cluster option '%s' "
523 "because '%s' is invalid", name, value);
524 value = NULL;
525 }
526
527 if (value) {
528 return value;
529 }
530 }
531
532 // No value found, use default
533 value = def_value;
534
535 if (value == NULL) {
536 crm_trace("No value or default provided for cluster option '%s'",
537 name);
538 return NULL;
539 }
540
541 if (validate) {
542 CRM_CHECK(validate(value) != FALSE,
543 crm_err("Bug: default value for cluster option '%s' is invalid", name);
544 return NULL);
545 }
546
547 crm_trace("Using default value '%s' for cluster option '%s'",
548 value, name);
549 if (options) {
550 new_value = strdup(value);
551 g_hash_table_insert(options, strdup(name), new_value);
552 value = new_value;
553 }
554 return value;
555}
556
567const char *
568pcmk__cluster_option(GHashTable *options,
569 const pcmk__cluster_option_t *option_list,
570 int len, const char *name)
571{
572 const char *value = NULL;
573
574 for (int lpc = 0; lpc < len; lpc++) {
575 if (pcmk__str_eq(name, option_list[lpc].name, pcmk__str_casei)) {
576 value = cluster_option_value(options, option_list[lpc].is_valid,
577 option_list[lpc].name,
578 option_list[lpc].alt_name,
579 option_list[lpc].default_value);
580 return value;
581 }
582 }
583 CRM_CHECK(FALSE, crm_err("Bug: looking for unknown option '%s'", name));
584 return NULL;
585}
586
598static void
599add_desc(GString *s, const char *tag, const char *desc, const char *values,
600 const char *spaces)
601{
602 char *escaped_en = crm_xml_escape(desc);
603
604 if (spaces != NULL) {
605 g_string_append(s, spaces);
606 }
607 pcmk__g_strcat(s, "<", tag, " lang=\"en\">", escaped_en, NULL);
608
609 if (values != NULL) {
610 pcmk__g_strcat(s, " Allowed values: ", values, NULL);
611 }
612 pcmk__g_strcat(s, "</", tag, ">\n", NULL);
613
614#ifdef ENABLE_NLS
615 {
616 static const char *locale = NULL;
617
618 char *localized = crm_xml_escape(_(desc));
619
620 if (strcmp(escaped_en, localized) != 0) {
621 if (locale == NULL) {
622 locale = strtok(setlocale(LC_ALL, NULL), "_");
623 }
624
625 if (spaces != NULL) {
626 g_string_append(s, spaces);
627 }
628 pcmk__g_strcat(s, "<", tag, " lang=\"", locale, "\">", localized,
629 NULL);
630
631 if (values != NULL) {
632 pcmk__g_strcat(s, _(" Allowed values: "), _(values), NULL);
633 }
634 pcmk__g_strcat(s, "</", tag, ">\n", NULL);
635 }
636 free(localized);
637 }
638#endif
639
640 free(escaped_en);
641}
642
643gchar *
644pcmk__format_option_metadata(const char *name, const char *desc_short,
645 const char *desc_long,
646 pcmk__cluster_option_t *option_list, int len)
647{
648 /* big enough to hold "pacemaker-schedulerd metadata" output */
649 GString *s = g_string_sized_new(13000);
650
652 "<?xml version=\"1.0\"?>\n"
653 "<resource-agent name=\"", name, "\" "
654 "version=\"" PACEMAKER_VERSION "\">\n"
655 " <version>" PCMK_OCF_VERSION "</version>\n", NULL);
656
657 add_desc(s, "longdesc", desc_long, NULL, " ");
658 add_desc(s, "shortdesc", desc_short, NULL, " ");
659
660 g_string_append(s, " <parameters>\n");
661
662 for (int lpc = 0; lpc < len; lpc++) {
663 const char *opt_name = option_list[lpc].name;
664 const char *opt_type = option_list[lpc].type;
665 const char *opt_values = option_list[lpc].values;
666 const char *opt_default = option_list[lpc].default_value;
667 const char *opt_desc_short = option_list[lpc].description_short;
668 const char *opt_desc_long = option_list[lpc].description_long;
669
670 // The standard requires long and short parameter descriptions
671 CRM_ASSERT((opt_desc_short != NULL) || (opt_desc_long != NULL));
672
673 if (opt_desc_short == NULL) {
674 opt_desc_short = opt_desc_long;
675 } else if (opt_desc_long == NULL) {
676 opt_desc_long = opt_desc_short;
677 }
678
679 // The standard requires a parameter type
680 CRM_ASSERT(opt_type != NULL);
681
682 pcmk__g_strcat(s, " <parameter name=\"", opt_name, "\">\n", NULL);
683
684 add_desc(s, "longdesc", opt_desc_long, opt_values, " ");
685 add_desc(s, "shortdesc", opt_desc_short, NULL, " ");
686
687 pcmk__g_strcat(s, " <content type=\"", opt_type, "\"", NULL);
688 if (opt_default != NULL) {
689 pcmk__g_strcat(s, " default=\"", opt_default, "\"", NULL);
690 }
691
692 if ((opt_values != NULL) && (strcmp(opt_type, "select") == 0)) {
693 char *str = strdup(opt_values);
694 const char *delim = ", ";
695 char *ptr = strtok(str, delim);
696
697 g_string_append(s, ">\n");
698
699 while (ptr != NULL) {
700 pcmk__g_strcat(s, " <option value=\"", ptr, "\" />\n",
701 NULL);
702 ptr = strtok(NULL, delim);
703 }
704 g_string_append_printf(s, " </content>\n");
705 free(str);
706
707 } else {
708 g_string_append(s, "/>\n");
709 }
710
711 g_string_append(s, " </parameter>\n");
712 }
713 g_string_append(s, " </parameters>\n</resource-agent>\n");
714
715 return g_string_free(s, FALSE);
716}
717
718void
720 pcmk__cluster_option_t *option_list, int len)
721{
722 for (int lpc = 0; lpc < len; lpc++) {
723 cluster_option_value(options, option_list[lpc].is_valid,
724 option_list[lpc].name,
725 option_list[lpc].alt_name,
726 option_list[lpc].default_value);
727 }
728}
#define PCMK_OCF_VERSION
Definition: agents.h:52
const char * name
Definition: cib.c:24
#define PCMK__NELEM(a)
Definition: internal.h:41
uint64_t flags
Definition: remote.c:3
bool pcmk_str_is_infinity(const char *s)
Definition: utils.c:543
char guint crm_parse_interval_spec(const char *input)
Parse milliseconds from a Pacemaker interval specification.
Definition: utils.c:271
gboolean crm_is_true(const char *s)
Definition: strings.c:416
int crm_str_to_boolean(const char *s, int *ret)
Definition: strings.c:427
bool pcmk_str_is_minus_infinity(const char *s)
Definition: utils.c:548
#define PACKAGE_BUGREPORT
Definition: config.h:508
#define PACEMAKER_VERSION
Definition: config.h:502
#define CRM_FEATURES
Definition: config.h:33
#define BUILD_VERSION
Definition: config.h:8
A dumping ground.
char * crm_system_name
Definition: utils.c:51
#define _(String)
Definition: crm_internal.h:53
#define NAME_MAX
Definition: logging.c:103
#define crm_notice(fmt, args...)
Definition: logging.h:361
#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
#define pcmk__config_warn(fmt...)
#define pcmk__config_err(fmt...)
bool pcmk__valid_interval_spec(const char *value)
Definition: options.c:399
void pcmk__cli_option_cleanup(void)
Definition: options.c:39
void pcmk__set_env_option(const char *option, const char *value)
Set or unset a Pacemaker environment variable option.
Definition: options.c:333
bool pcmk__valid_boolean(const char *value)
Definition: options.c:406
bool pcmk__valid_number(const char *value)
Definition: options.c:414
bool pcmk__env_option_enabled(const char *daemon, const char *option)
Definition: options.c:384
gchar * 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:644
bool pcmk__valid_quorum(const char *value)
Definition: options.c:437
bool pcmk__valid_percentage(const char *value)
Definition: options.c:470
const char * pcmk__cluster_option(GHashTable *options, const pcmk__cluster_option_t *option_list, int len, const char *name)
Definition: options.c:568
const char * pcmk__env_option(const char *option)
Definition: options.c:290
void pcmk__validate_cluster_options(GHashTable *options, pcmk__cluster_option_t *option_list, int len)
Definition: options.c:719
void pcmk__set_cli_options(const char *short_options, const char *app_usage, const pcmk__cli_option_t *long_options, const char *app_desc)
Definition: options.c:105
bool pcmk__valid_positive_number(const char *value)
Definition: options.c:428
void pcmk__cli_help(char cmd, crm_exit_t exit_code)
Definition: options.c:195
int pcmk__next_cli_option(int argc, char **argv, int *index, const char **longname)
Definition: options.c:148
bool pcmk__valid_script(const char *value)
Definition: options.c:443
#define no_argument
#define required_argument
@ pcmk__option_example
@ pcmk__option_hidden
@ pcmk__option_paragraph
stonith_t * st
Definition: pcmk_fence.c:28
char * strerror(int errnum)
int setenv(const char *name, const char *value, int why)
int daemon(int nochdir, int noclose)
#define CRM_ASSERT(expr)
Definition: results.h:42
@ CRM_EX_OK
Success.
Definition: results.h:234
@ CRM_EX_USAGE
Command line usage error.
Definition: results.h:248
_Noreturn crm_exit_t crm_exit(crm_exit_t rc)
Definition: results.c:856
@ pcmk_rc_ok
Definition: results.h:148
enum crm_exit_e crm_exit_t
int pcmk__scan_ll(const char *text, long long *result, long long default_value)
Definition: strings.c:97
bool pcmk__strcase_any_of(const char *s,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:928
@ pcmk__str_casei
void pcmk__g_strcat(GString *buffer,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1214
const char * description_short
const char * description_long
char * crm_xml_escape(const char *text)
Replace special characters with their XML escape sequences.
Definition: xml.c:1406