pacemaker  2.1.9-49aab99839
Scalable High-Availability cluster resource manager
cmdline.c
Go to the documentation of this file.
1 /*
2  * Copyright 2019-2024 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 #include <ctype.h>
13 #include <glib.h>
14 
15 #include <crm/crm.h>
18 #include <crm/common/util.h>
19 
20 static gboolean
21 bump_verbosity(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
22  pcmk__common_args_t *common_args = (pcmk__common_args_t *) data;
23  common_args->verbosity++;
24  return TRUE;
25 }
26 
28 pcmk__new_common_args(const char *summary)
29 {
30  pcmk__common_args_t *args = NULL;
31 
32  args = calloc(1, sizeof(pcmk__common_args_t));
33  if (args == NULL) {
35  }
36 
37  args->summary = strdup(summary);
38  if (args->summary == NULL) {
39  free(args);
40  args = NULL;
42  }
43 
44  return args;
45 }
46 
47 static void
48 free_common_args(gpointer data) {
49  pcmk__common_args_t *common_args = (pcmk__common_args_t *) data;
50 
51  free(common_args->summary);
52  free(common_args->output_ty);
53  free(common_args->output_dest);
54 
55  if (common_args->output_as_descr != NULL) {
56  free(common_args->output_as_descr);
57  }
58 
59  free(common_args);
60 }
61 
62 GOptionContext *
63 pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts,
64  GOptionGroup **output_group, const char *param_string) {
65  GOptionContext *context;
66  GOptionGroup *main_group;
67 
68  GOptionEntry main_entries[3] = {
69  { "version", '$', 0, G_OPTION_ARG_NONE, &(common_args->version),
70  N_("Display software version and exit"),
71  NULL },
72  { "verbose", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, bump_verbosity,
73  N_("Increase debug output (may be specified multiple times)"),
74  NULL },
75 
76  { NULL }
77  };
78 
79  main_group = g_option_group_new(NULL, "Application Options:", NULL, common_args, free_common_args);
80  g_option_group_add_entries(main_group, main_entries);
81 
82  context = g_option_context_new(param_string);
83  g_option_context_set_summary(context, common_args->summary);
84  g_option_context_set_description(context,
85  "Report bugs to " PCMK__BUG_URL "\n");
86  g_option_context_set_main_group(context, main_group);
87 
88  if (fmts != NULL) {
89  GOptionEntry output_entries[3] = {
90  { "output-as", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_ty),
91  NULL,
92  N_("FORMAT") },
93  { "output-to", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_dest),
94  N_( "Specify file name for output (or \"-\" for stdout)"), N_("DEST") },
95 
96  { NULL }
97  };
98 
99  if (*output_group == NULL) {
100  *output_group = g_option_group_new("output", N_("Output Options:"), N_("Show output help"), NULL, NULL);
101  }
102 
103  common_args->output_as_descr = crm_strdup_printf("Specify output format as one of: %s", fmts);
104  output_entries[0].description = common_args->output_as_descr;
105  g_option_group_add_entries(*output_group, output_entries);
106  g_option_context_add_group(context, *output_group);
107  }
108 
109  // main_group is now owned by context, we don't free it here
110  // cppcheck-suppress memleak
111  return context;
112 }
113 
114 void
115 pcmk__free_arg_context(GOptionContext *context) {
116  if (context == NULL) {
117  return;
118  }
119 
120  g_option_context_free(context);
121 }
122 
123 void
124 pcmk__add_main_args(GOptionContext *context, const GOptionEntry entries[])
125 {
126  GOptionGroup *main_group = g_option_context_get_main_group(context);
127 
128  g_option_group_add_entries(main_group, entries);
129 }
130 
131 void
132 pcmk__add_arg_group(GOptionContext *context, const char *name,
133  const char *header, const char *desc,
134  const GOptionEntry entries[])
135 {
136  GOptionGroup *group = NULL;
137 
138  group = g_option_group_new(name, header, desc, NULL, NULL);
139  g_option_group_add_entries(group, entries);
140  g_option_context_add_group(context, group);
141  // group is now owned by context, we don't free it here
142  // cppcheck-suppress memleak
143 }
144 
145 static gchar *
146 string_replace(gchar *str, const gchar *sub, const gchar *repl)
147 {
148  /* This function just replaces all occurrences of a substring
149  * with some other string. It doesn't handle cases like overlapping,
150  * so don't get clever with it.
151  *
152  * FIXME: When glib >= 2.68 is supported, we can get rid of this
153  * function and use g_string_replace instead.
154  */
155  gchar **split = g_strsplit(str, sub, 0);
156  gchar *retval = g_strjoinv(repl, split);
157 
158  g_strfreev(split);
159  return retval;
160 }
161 
162 gchar *
163 pcmk__quote_cmdline(gchar **argv)
164 {
165  GString *gs = NULL;
166 
167  if (argv == NULL || argv[0] == NULL) {
168  return NULL;
169  }
170 
171  gs = g_string_sized_new(100);
172 
173  for (int i = 0; argv[i] != NULL; i++) {
174  if (i > 0) {
175  g_string_append_c(gs, ' ');
176  }
177 
178  if (strchr(argv[i], ' ') == NULL) {
179  /* The arg does not contain a space. */
180  g_string_append(gs, argv[i]);
181  } else if (strchr(argv[i], '\'') == NULL) {
182  /* The arg contains a space, but not a single quote. */
183  pcmk__g_strcat(gs, "'", argv[i], "'", NULL);
184  } else {
185  /* The arg contains both a space and a single quote, which needs to
186  * be replaced with an escaped version. We do this instead of counting
187  * on libxml to handle the escaping for various reasons:
188  *
189  * (1) This keeps the string as valid shell.
190  * (2) We don't want to use XML entities in formats besides XML and HTML.
191  * (3) The string we are feeding to libxml is something like: "a b 'c d' e".
192  * It won't escape the single quotes around 'c d' here because there is
193  * no need to escape quotes inside a different form of quote. If we
194  * change the string to "a b 'c'd' e", we haven't changed anything - it's
195  * still single quotes inside double quotes.
196  *
197  * On the other hand, if we replace the single quote with "&apos;", then
198  * we have introduced an ampersand which libxml will escape. This leaves
199  * us with "&amp;apos;" which is not what we want.
200  *
201  * It's simplest to just escape with a backslash.
202  */
203  gchar *repl = string_replace(argv[i], "'", "\\\'");
204  pcmk__g_strcat(gs, "'", repl, "'", NULL);
205  g_free(repl);
206  }
207  }
208 
209  return g_string_free(gs, FALSE);
210 }
211 
212 gchar **
213 pcmk__cmdline_preproc(char *const *argv, const char *special) {
214  GPtrArray *arr = NULL;
215  bool saw_dash_dash = false;
216  bool copy_option = false;
217 
218  if (argv == NULL) {
219  return NULL;
220  }
221 
222  if (g_get_prgname() == NULL && argv && *argv) {
223  gchar *basename = g_path_get_basename(*argv);
224 
225  g_set_prgname(basename);
226  g_free(basename);
227  }
228 
229  arr = g_ptr_array_new();
230 
231  for (int i = 0; argv[i] != NULL; i++) {
232  /* If this is the first time we saw "--" in the command line, set
233  * a flag so we know to just copy everything after it over. We also
234  * want to copy the "--" over so whatever actually parses the command
235  * line when we're done knows where arguments end.
236  */
237  if (saw_dash_dash == false && strcmp(argv[i], "--") == 0) {
238  saw_dash_dash = true;
239  }
240 
241  if (saw_dash_dash == true) {
242  g_ptr_array_add(arr, g_strdup(argv[i]));
243  continue;
244  }
245 
246  if (copy_option == true) {
247  g_ptr_array_add(arr, g_strdup(argv[i]));
248  copy_option = false;
249  continue;
250  }
251 
252  /* This is just a dash by itself. That could indicate stdin/stdout, or
253  * it could be user error. Copy it over and let glib figure it out.
254  */
255  if (pcmk__str_eq(argv[i], "-", pcmk__str_casei)) {
256  g_ptr_array_add(arr, g_strdup(argv[i]));
257  continue;
258  }
259 
260  /* "-INFINITY" is almost certainly meant as a string, not as an option
261  * list
262  */
263  if (strcmp(argv[i], "-INFINITY") == 0) {
264  g_ptr_array_add(arr, g_strdup(argv[i]));
265  continue;
266  }
267 
268  /* This is a short argument, or perhaps several. Iterate over it
269  * and explode them out into individual arguments.
270  */
271  if (g_str_has_prefix(argv[i], "-") && !g_str_has_prefix(argv[i], "--")) {
272  /* Skip over leading dash */
273  const char *ch = argv[i]+1;
274 
275  /* This looks like the start of a number, which means it is a negative
276  * number. It's probably the argument to the preceeding option, but
277  * we can't know that here. Copy it over and let whatever handles
278  * arguments next figure it out.
279  */
280  if (*ch != '\0' && *ch >= '1' && *ch <= '9') {
281  bool is_numeric = true;
282 
283  while (*ch != '\0') {
284  if (!isdigit(*ch)) {
285  is_numeric = false;
286  break;
287  }
288 
289  ch++;
290  }
291 
292  if (is_numeric) {
293  g_ptr_array_add(arr, g_strdup_printf("%s", argv[i]));
294  continue;
295  } else {
296  /* This argument wasn't entirely numeric. Reset ch to the
297  * beginning so we can process it one character at a time.
298  */
299  ch = argv[i]+1;
300  }
301  }
302 
303  while (*ch != '\0') {
304  /* This is a special short argument that takes an option. getopt
305  * allows values to be interspersed with a list of arguments, but
306  * glib does not. Grab both the argument and its value and
307  * separate them into a new argument.
308  */
309  if (special != NULL && strchr(special, *ch) != NULL) {
310  /* The argument does not occur at the end of this string of
311  * arguments. Take everything through the end as its value.
312  */
313  if (*(ch+1) != '\0') {
314  fprintf(stderr, "Deprecated argument format '-%c%s' used.\n", *ch, ch+1);
315  fprintf(stderr, "Please use '-%c %s' instead. "
316  "Support will be removed in a future release.\n",
317  *ch, ch+1);
318 
319  g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
320  g_ptr_array_add(arr, g_strdup(ch+1));
321  break;
322 
323  /* The argument occurs at the end of this string. Hopefully
324  * whatever comes next in argv is its value. It may not be,
325  * but that is not for us to decide.
326  */
327  } else {
328  g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
329  copy_option = true;
330  ch++;
331  }
332 
333  /* This is a regular short argument. Just copy it over. */
334  } else {
335  g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
336  ch++;
337  }
338  }
339 
340  /* This is a long argument, or an option, or something else.
341  * Copy it over - everything else is copied, so this keeps it easy for
342  * the caller to know what to do with the memory when it's done.
343  */
344  } else {
345  g_ptr_array_add(arr, g_strdup(argv[i]));
346  }
347  }
348 
349  g_ptr_array_add(arr, NULL);
350 
351  return (char **) g_ptr_array_free(arr, FALSE);
352 }
353 
354 G_GNUC_PRINTF(3, 4)
355 gboolean
356 pcmk__force_args(GOptionContext *context, GError **error, const char *format, ...) {
357  int len = 0;
358  char *buf = NULL;
359  gchar **extra_args = NULL;
360  va_list ap;
361  gboolean retval = TRUE;
362 
363  va_start(ap, format);
364  len = vasprintf(&buf, format, ap);
365  pcmk__assert(len > 0);
366  va_end(ap);
367 
368  if (!g_shell_parse_argv(buf, NULL, &extra_args, error)) {
369  g_strfreev(extra_args);
370  free(buf);
371  return FALSE;
372  }
373 
374  retval = g_option_context_parse_strv(context, &extra_args, error);
375 
376  g_strfreev(extra_args);
377  free(buf);
378  return retval;
379 }
A dumping ground.
char data[0]
Definition: cpg.c:58
_Noreturn crm_exit_t crm_exit(crm_exit_t rc)
Definition: results.c:938
const char * name
Definition: cib.c:26
gchar ** pcmk__cmdline_preproc(char *const *argv, const char *special)
Definition: cmdline.c:213
gchar * pcmk__quote_cmdline(gchar **argv)
Definition: cmdline.c:163
GOptionContext * pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts, GOptionGroup **output_group, const char *param_string)
Definition: cmdline.c:63
Utility functions.
External (OS/environmental) problem.
Definition: results.h:272
void pcmk__g_strcat(GString *buffer,...) G_GNUC_NULL_TERMINATED
Definition: strings.c:1308
pcmk__common_args_t * pcmk__new_common_args(const char *summary)
Definition: cmdline.c:28
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
void pcmk__add_arg_group(GOptionContext *context, const char *name, const char *header, const char *desc, const GOptionEntry entries[])
Definition: cmdline.c:132
void pcmk__free_arg_context(GOptionContext *context)
Definition: cmdline.c:115
gboolean pcmk__force_args(GOptionContext *context, GError **error, const char *format,...)
Definition: cmdline.c:356
#define PCMK__BUG_URL
Definition: config.h:550
#define pcmk__assert(expr)
#define N_(String)
Definition: crm_internal.h:54
unsigned int verbosity
void pcmk__add_main_args(GOptionContext *context, const GOptionEntry entries[])
Definition: cmdline.c:124