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