pacemaker 3.0.1-16e74fc4da
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
cmdline.c
Go to the documentation of this file.
1/*
2 * Copyright 2019-2025 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
20static gboolean
21bump_verbosity(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
23 common_args->verbosity++;
24 return TRUE;
25}
26
28pcmk__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 // cppcheck-suppress nullPointerOutOfMemory
38 args->summary = strdup(summary);
39 // cppcheck-suppress nullPointerOutOfMemory
40 if (args->summary == NULL) {
41 free(args);
42 args = NULL;
44 }
45
46 return args;
47}
48
49static void
50free_common_args(gpointer data) {
52
53 free(common_args->summary);
54 free(common_args->output_ty);
55 free(common_args->output_dest);
56
57 if (common_args->output_as_descr != NULL) {
58 free(common_args->output_as_descr);
59 }
60
61 free(common_args);
62}
63
64GOptionContext *
65pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts,
66 GOptionGroup **output_group, const char *param_string) {
67 GOptionContext *context;
68 GOptionGroup *main_group;
69
70 GOptionEntry main_entries[3] = {
71 { "version", '$', 0, G_OPTION_ARG_NONE, &(common_args->version),
72 N_("Display software version and exit"),
73 NULL },
74 { "verbose", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, bump_verbosity,
75 N_("Increase debug output (may be specified multiple times)"),
76 NULL },
77
78 { NULL }
79 };
80
81 main_group = g_option_group_new(NULL, "Application Options:", NULL, common_args, free_common_args);
82 g_option_group_add_entries(main_group, main_entries);
83
84 context = g_option_context_new(param_string);
85 g_option_context_set_summary(context, common_args->summary);
86 g_option_context_set_description(context,
87 "Report bugs to " PCMK__BUG_URL "\n");
88 g_option_context_set_main_group(context, main_group);
89
90 if (fmts != NULL) {
91 GOptionEntry output_entries[3] = {
92 { "output-as", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_ty),
93 NULL,
94 N_("FORMAT") },
95 { "output-to", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_dest),
96 N_( "Specify file name for output (or \"-\" for stdout)"), N_("DEST") },
97
98 { NULL }
99 };
100
101 if (*output_group == NULL) {
102 *output_group = g_option_group_new("output", N_("Output Options:"), N_("Show output help"), NULL, NULL);
103 }
104
105 common_args->output_as_descr = crm_strdup_printf("Specify output format as one of: %s", fmts);
106 output_entries[0].description = common_args->output_as_descr;
107 g_option_group_add_entries(*output_group, output_entries);
108 g_option_context_add_group(context, *output_group);
109 }
110
111 // main_group is now owned by context, we don't free it here
112 return context;
113}
114
115void
116pcmk__free_arg_context(GOptionContext *context) {
117 if (context == NULL) {
118 return;
119 }
120
121 g_option_context_free(context);
122}
123
124void
125pcmk__add_main_args(GOptionContext *context, const GOptionEntry entries[])
126{
127 GOptionGroup *main_group = g_option_context_get_main_group(context);
128
129 g_option_group_add_entries(main_group, entries);
130}
131
132void
133pcmk__add_arg_group(GOptionContext *context, const char *name,
134 const char *header, const char *desc,
135 const GOptionEntry entries[])
136{
137 GOptionGroup *group = NULL;
138
139 group = g_option_group_new(name, header, desc, NULL, NULL);
140 g_option_group_add_entries(group, entries);
141 g_option_context_add_group(context, group);
142 // group is now owned by context, we don't free it here
143}
144
145static gchar *
146string_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
162gchar *
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
212gchar **
213pcmk__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
354G_GNUC_PRINTF(3, 4)
355gboolean
356pcmk__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}
const char * name
Definition cib.c:26
pcmk__common_args_t * pcmk__new_common_args(const char *summary)
Definition cmdline.c:28
void pcmk__add_arg_group(GOptionContext *context, const char *name, const char *header, const char *desc, const GOptionEntry entries[])
Definition cmdline.c:133
GOptionContext * pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts, GOptionGroup **output_group, const char *param_string)
Definition cmdline.c:65
void pcmk__free_arg_context(GOptionContext *context)
Definition cmdline.c:116
gchar * pcmk__quote_cmdline(gchar **argv)
Definition cmdline.c:163
void pcmk__add_main_args(GOptionContext *context, const GOptionEntry entries[])
Definition cmdline.c:125
gchar ** pcmk__cmdline_preproc(char *const *argv, const char *special)
Definition cmdline.c:213
gboolean pcmk__force_args(GOptionContext *context, GError **error, const char *format,...)
Definition cmdline.c:356
Utility functions.
#define PCMK__BUG_URL
Definition config.h:466
char data[0]
Definition cpg.c:10
A dumping ground.
#define N_(String)
@ CRM_EX_OSERR
External (OS/environmental) problem.
Definition results.h:254
_Noreturn crm_exit_t crm_exit(crm_exit_t rc)
Definition results.c:1058
#define pcmk__assert(expr)
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
@ pcmk__str_casei
void pcmk__g_strcat(GString *buffer,...) G_GNUC_NULL_TERMINATED
Definition strings.c:1299