/src/pacemaker/lib/common/cmdline.c
Line | Count | Source (jump to first uncovered line) |
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> |
16 | | #include <crm/common/cmdline_internal.h> |
17 | | #include <crm/common/strings_internal.h> |
18 | | #include <crm/common/util.h> |
19 | | |
20 | | static gboolean |
21 | 0 | bump_verbosity(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { |
22 | 0 | pcmk__common_args_t *common_args = (pcmk__common_args_t *) data; |
23 | 0 | common_args->verbosity++; |
24 | 0 | return TRUE; |
25 | 0 | } |
26 | | |
27 | | pcmk__common_args_t * |
28 | | pcmk__new_common_args(const char *summary) |
29 | 0 | { |
30 | 0 | pcmk__common_args_t *args = NULL; |
31 | |
|
32 | 0 | args = calloc(1, sizeof(pcmk__common_args_t)); |
33 | 0 | if (args == NULL) { |
34 | 0 | crm_exit(CRM_EX_OSERR); |
35 | 0 | } |
36 | |
|
37 | 0 | args->summary = strdup(summary); |
38 | 0 | if (args->summary == NULL) { |
39 | 0 | free(args); |
40 | 0 | args = NULL; |
41 | 0 | crm_exit(CRM_EX_OSERR); |
42 | 0 | } |
43 | |
|
44 | 0 | return args; |
45 | 0 | } |
46 | | |
47 | | static void |
48 | 0 | free_common_args(gpointer data) { |
49 | 0 | pcmk__common_args_t *common_args = (pcmk__common_args_t *) data; |
50 | |
|
51 | 0 | free(common_args->summary); |
52 | 0 | free(common_args->output_ty); |
53 | 0 | free(common_args->output_dest); |
54 | |
|
55 | 0 | if (common_args->output_as_descr != NULL) { |
56 | 0 | free(common_args->output_as_descr); |
57 | 0 | } |
58 | |
|
59 | 0 | free(common_args); |
60 | 0 | } |
61 | | |
62 | | GOptionContext * |
63 | | pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts, |
64 | 0 | GOptionGroup **output_group, const char *param_string) { |
65 | 0 | GOptionContext *context; |
66 | 0 | GOptionGroup *main_group; |
67 | |
|
68 | 0 | GOptionEntry main_entries[3] = { |
69 | 0 | { "version", '$', 0, G_OPTION_ARG_NONE, &(common_args->version), |
70 | 0 | N_("Display software version and exit"), |
71 | 0 | NULL }, |
72 | 0 | { "verbose", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, bump_verbosity, |
73 | 0 | N_("Increase debug output (may be specified multiple times)"), |
74 | 0 | NULL }, |
75 | |
|
76 | 0 | { NULL } |
77 | 0 | }; |
78 | |
|
79 | 0 | main_group = g_option_group_new(NULL, "Application Options:", NULL, common_args, free_common_args); |
80 | 0 | g_option_group_add_entries(main_group, main_entries); |
81 | |
|
82 | 0 | context = g_option_context_new(param_string); |
83 | 0 | g_option_context_set_summary(context, common_args->summary); |
84 | 0 | g_option_context_set_description(context, |
85 | 0 | "Report bugs to " PCMK__BUG_URL "\n"); |
86 | 0 | g_option_context_set_main_group(context, main_group); |
87 | |
|
88 | 0 | if (fmts != NULL) { |
89 | 0 | GOptionEntry output_entries[3] = { |
90 | 0 | { "output-as", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_ty), |
91 | 0 | NULL, |
92 | 0 | N_("FORMAT") }, |
93 | 0 | { "output-to", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_dest), |
94 | 0 | N_( "Specify file name for output (or \"-\" for stdout)"), N_("DEST") }, |
95 | |
|
96 | 0 | { NULL } |
97 | 0 | }; |
98 | |
|
99 | 0 | if (*output_group == NULL) { |
100 | 0 | *output_group = g_option_group_new("output", N_("Output Options:"), N_("Show output help"), NULL, NULL); |
101 | 0 | } |
102 | |
|
103 | 0 | common_args->output_as_descr = crm_strdup_printf("Specify output format as one of: %s", fmts); |
104 | 0 | output_entries[0].description = common_args->output_as_descr; |
105 | 0 | g_option_group_add_entries(*output_group, output_entries); |
106 | 0 | g_option_context_add_group(context, *output_group); |
107 | 0 | } |
108 | | |
109 | | // main_group is now owned by context, we don't free it here |
110 | | // cppcheck-suppress memleak |
111 | 0 | return context; |
112 | 0 | } |
113 | | |
114 | | void |
115 | 0 | pcmk__free_arg_context(GOptionContext *context) { |
116 | 0 | if (context == NULL) { |
117 | 0 | return; |
118 | 0 | } |
119 | | |
120 | 0 | g_option_context_free(context); |
121 | 0 | } |
122 | | |
123 | | void |
124 | | pcmk__add_main_args(GOptionContext *context, const GOptionEntry entries[]) |
125 | 0 | { |
126 | 0 | GOptionGroup *main_group = g_option_context_get_main_group(context); |
127 | |
|
128 | 0 | g_option_group_add_entries(main_group, entries); |
129 | 0 | } |
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 | 0 | { |
136 | 0 | GOptionGroup *group = NULL; |
137 | |
|
138 | 0 | group = g_option_group_new(name, header, desc, NULL, NULL); |
139 | 0 | g_option_group_add_entries(group, entries); |
140 | 0 | 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 | 0 | } |
144 | | |
145 | | static gchar * |
146 | | string_replace(gchar *str, const gchar *sub, const gchar *repl) |
147 | 0 | { |
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 | 0 | gchar **split = g_strsplit(str, sub, 0); |
156 | 0 | gchar *retval = g_strjoinv(repl, split); |
157 | |
|
158 | 0 | g_strfreev(split); |
159 | 0 | return retval; |
160 | 0 | } |
161 | | |
162 | | gchar * |
163 | | pcmk__quote_cmdline(gchar **argv) |
164 | 0 | { |
165 | 0 | GString *gs = NULL; |
166 | |
|
167 | 0 | if (argv == NULL || argv[0] == NULL) { |
168 | 0 | return NULL; |
169 | 0 | } |
170 | | |
171 | 0 | gs = g_string_sized_new(100); |
172 | |
|
173 | 0 | for (int i = 0; argv[i] != NULL; i++) { |
174 | 0 | if (i > 0) { |
175 | 0 | g_string_append_c(gs, ' '); |
176 | 0 | } |
177 | |
|
178 | 0 | if (strchr(argv[i], ' ') == NULL) { |
179 | | /* The arg does not contain a space. */ |
180 | 0 | g_string_append(gs, argv[i]); |
181 | 0 | } else if (strchr(argv[i], '\'') == NULL) { |
182 | | /* The arg contains a space, but not a single quote. */ |
183 | 0 | pcmk__g_strcat(gs, "'", argv[i], "'", NULL); |
184 | 0 | } 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 "'", then |
198 | | * we have introduced an ampersand which libxml will escape. This leaves |
199 | | * us with "&apos;" which is not what we want. |
200 | | * |
201 | | * It's simplest to just escape with a backslash. |
202 | | */ |
203 | 0 | gchar *repl = string_replace(argv[i], "'", "\\\'"); |
204 | 0 | pcmk__g_strcat(gs, "'", repl, "'", NULL); |
205 | 0 | g_free(repl); |
206 | 0 | } |
207 | 0 | } |
208 | |
|
209 | 0 | return g_string_free(gs, FALSE); |
210 | 0 | } |
211 | | |
212 | | gchar ** |
213 | 0 | pcmk__cmdline_preproc(char *const *argv, const char *special) { |
214 | 0 | GPtrArray *arr = NULL; |
215 | 0 | bool saw_dash_dash = false; |
216 | 0 | bool copy_option = false; |
217 | |
|
218 | 0 | if (argv == NULL) { |
219 | 0 | return NULL; |
220 | 0 | } |
221 | | |
222 | 0 | if (g_get_prgname() == NULL && argv && *argv) { |
223 | 0 | gchar *basename = g_path_get_basename(*argv); |
224 | |
|
225 | 0 | g_set_prgname(basename); |
226 | 0 | g_free(basename); |
227 | 0 | } |
228 | |
|
229 | 0 | arr = g_ptr_array_new(); |
230 | |
|
231 | 0 | 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 | 0 | if (saw_dash_dash == false && strcmp(argv[i], "--") == 0) { |
238 | 0 | saw_dash_dash = true; |
239 | 0 | } |
240 | |
|
241 | 0 | if (saw_dash_dash == true) { |
242 | 0 | g_ptr_array_add(arr, g_strdup(argv[i])); |
243 | 0 | continue; |
244 | 0 | } |
245 | | |
246 | 0 | if (copy_option == true) { |
247 | 0 | g_ptr_array_add(arr, g_strdup(argv[i])); |
248 | 0 | copy_option = false; |
249 | 0 | continue; |
250 | 0 | } |
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 | 0 | if (pcmk__str_eq(argv[i], "-", pcmk__str_casei)) { |
256 | 0 | g_ptr_array_add(arr, g_strdup(argv[i])); |
257 | 0 | continue; |
258 | 0 | } |
259 | | |
260 | | /* "-INFINITY" is almost certainly meant as a string, not as an option |
261 | | * list |
262 | | */ |
263 | 0 | if (strcmp(argv[i], "-INFINITY") == 0) { |
264 | 0 | g_ptr_array_add(arr, g_strdup(argv[i])); |
265 | 0 | continue; |
266 | 0 | } |
267 | | |
268 | | /* This is a short argument, or perhaps several. Iterate over it |
269 | | * and explode them out into individual arguments. |
270 | | */ |
271 | 0 | if (g_str_has_prefix(argv[i], "-") && !g_str_has_prefix(argv[i], "--")) { |
272 | | /* Skip over leading dash */ |
273 | 0 | 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 | 0 | if (*ch != '\0' && *ch >= '1' && *ch <= '9') { |
281 | 0 | bool is_numeric = true; |
282 | |
|
283 | 0 | while (*ch != '\0') { |
284 | 0 | if (!isdigit(*ch)) { |
285 | 0 | is_numeric = false; |
286 | 0 | break; |
287 | 0 | } |
288 | | |
289 | 0 | ch++; |
290 | 0 | } |
291 | |
|
292 | 0 | if (is_numeric) { |
293 | 0 | g_ptr_array_add(arr, g_strdup_printf("%s", argv[i])); |
294 | 0 | continue; |
295 | 0 | } 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 | 0 | ch = argv[i]+1; |
300 | 0 | } |
301 | 0 | } |
302 | | |
303 | 0 | 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 | 0 | 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 | 0 | if (*(ch+1) != '\0') { |
314 | 0 | fprintf(stderr, "Deprecated argument format '-%c%s' used.\n", *ch, ch+1); |
315 | 0 | fprintf(stderr, "Please use '-%c %s' instead. " |
316 | 0 | "Support will be removed in a future release.\n", |
317 | 0 | *ch, ch+1); |
318 | |
|
319 | 0 | g_ptr_array_add(arr, g_strdup_printf("-%c", *ch)); |
320 | 0 | g_ptr_array_add(arr, g_strdup(ch+1)); |
321 | 0 | 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 | 0 | } else { |
328 | 0 | g_ptr_array_add(arr, g_strdup_printf("-%c", *ch)); |
329 | 0 | copy_option = true; |
330 | 0 | ch++; |
331 | 0 | } |
332 | | |
333 | | /* This is a regular short argument. Just copy it over. */ |
334 | 0 | } else { |
335 | 0 | g_ptr_array_add(arr, g_strdup_printf("-%c", *ch)); |
336 | 0 | ch++; |
337 | 0 | } |
338 | 0 | } |
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 | 0 | } else { |
345 | 0 | g_ptr_array_add(arr, g_strdup(argv[i])); |
346 | 0 | } |
347 | 0 | } |
348 | |
|
349 | 0 | g_ptr_array_add(arr, NULL); |
350 | |
|
351 | 0 | return (char **) g_ptr_array_free(arr, FALSE); |
352 | 0 | } |
353 | | |
354 | | G_GNUC_PRINTF(3, 4) |
355 | | gboolean |
356 | 0 | pcmk__force_args(GOptionContext *context, GError **error, const char *format, ...) { |
357 | 0 | int len = 0; |
358 | 0 | char *buf = NULL; |
359 | 0 | gchar **extra_args = NULL; |
360 | 0 | va_list ap; |
361 | 0 | gboolean retval = TRUE; |
362 | |
|
363 | 0 | va_start(ap, format); |
364 | 0 | len = vasprintf(&buf, format, ap); |
365 | 0 | pcmk__assert(len > 0); |
366 | 0 | va_end(ap); |
367 | |
|
368 | 0 | if (!g_shell_parse_argv(buf, NULL, &extra_args, error)) { |
369 | 0 | g_strfreev(extra_args); |
370 | 0 | free(buf); |
371 | 0 | return FALSE; |
372 | 0 | } |
373 | | |
374 | 0 | retval = g_option_context_parse_strv(context, &extra_args, error); |
375 | |
|
376 | 0 | g_strfreev(extra_args); |
377 | 0 | free(buf); |
378 | 0 | return retval; |
379 | 0 | } |