pacemaker 2.1.4-dc6eb4362e
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 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(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 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
146int
147pcmk__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
191void
192pcmk__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
285const char *
286pcmk__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
318void
319pcmk__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
355bool
356pcmk__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
368bool
370{
371 (void) crm_parse_interval_spec(value);
372 return errno == 0;
373}
374
375bool
376pcmk__valid_boolean(const char *value)
377{
378 int tmp;
379
380 return crm_str_to_boolean(value, &tmp) == 1;
381}
382
383bool
384pcmk__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
397bool
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
406bool
407pcmk__valid_quorum(const char *value)
408{
409 return pcmk__strcase_any_of(value, "stop", "freeze", "ignore", "demote", "suicide", NULL);
410}
411
412bool
413pcmk__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
439bool
440pcmk__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
463static const char *
464cluster_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
537const char *
538pcmk__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
565static void
566add_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
606char *
607pcmk__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
682void
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 PCMK_OCF_VERSION
Definition: agents.h:42
const char * name
Definition: cib.c:24
uint64_t flags
Definition: remote.c:3
bool pcmk_str_is_infinity(const char *s)
Definition: utils.c:513
char guint crm_parse_interval_spec(const char *input)
Parse milliseconds from a Pacemaker interval specification.
Definition: utils.c:242
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:518
#define PACKAGE_BUGREPORT
Definition: config.h:507
#define PACEMAKER_VERSION
Definition: config.h:501
#define CRM_FEATURES
Definition: config.h:33
#define BUILD_VERSION
Definition: config.h:8
enum crm_ais_msg_types type
Definition: cpg.c:3
A dumping ground.
char * crm_system_name
Definition: utils.c:54
#define _(String)
Definition: crm_internal.h:53
#define NAME_MAX
Definition: logging.c:103
#define crm_notice(fmt, args...)
Definition: logging.h:360
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:226
#define crm_err(fmt, args...)
Definition: logging.h:358
#define crm_trace(fmt, args...)
Definition: logging.h:364
#define pcmk__config_warn(fmt...)
#define pcmk__config_err(fmt...)
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
bool pcmk__valid_interval_spec(const char *value)
Definition: options.c:369
void pcmk__set_env_option(const char *option, const char *value)
Set or unset a Pacemaker environment variable option.
Definition: options.c:319
bool pcmk__valid_boolean(const char *value)
Definition: options.c:376
bool pcmk__valid_number(const char *value)
Definition: options.c:384
const char * pcmk__cluster_option(GHashTable *options, pcmk__cluster_option_t *option_list, int len, const char *name)
Definition: options.c:538
bool pcmk__env_option_enabled(const char *daemon, const char *option)
Definition: options.c:356
bool pcmk__valid_quorum(const char *value)
Definition: options.c:407
bool pcmk__valid_percentage(const char *value)
Definition: options.c:440
const char * pcmk__env_option(const char *option)
Definition: options.c:286
void pcmk__validate_cluster_options(GHashTable *options, pcmk__cluster_option_t *option_list, int len)
Definition: options.c:683
bool pcmk__valid_positive_number(const char *value)
Definition: options.c:398
void pcmk__cli_help(char cmd, crm_exit_t exit_code)
Definition: options.c:192
void pcmk__cli_option_cleanup()
Definition: options.c:39
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__next_cli_option(int argc, char **argv, int *index, const char **longname)
Definition: options.c:147
bool pcmk__valid_script(const char *value)
Definition: options.c:413
#define no_argument
#define required_argument
@ pcmk__option_example
@ pcmk__option_hidden
@ pcmk__option_paragraph
stonith_t * st
Definition: pcmk_fence.c:27
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:239
@ CRM_EX_USAGE
Command line usage error.
Definition: results.h:251
_Noreturn crm_exit_t crm_exit(crm_exit_t rc)
Definition: results.c:775
@ pcmk_rc_ok
Definition: results.h:146
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:931
@ pcmk__str_casei
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:1345