Line data Source code
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>
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 : }
26 :
27 : pcmk__common_args_t *
28 85 : pcmk__new_common_args(const char *summary)
29 : {
30 85 : pcmk__common_args_t *args = NULL;
31 :
32 85 : args = calloc(1, sizeof(pcmk__common_args_t));
33 85 : if (args == NULL) {
34 1 : crm_exit(CRM_EX_OSERR);
35 : }
36 :
37 84 : args->summary = strdup(summary);
38 84 : if (args->summary == NULL) {
39 1 : free(args);
40 1 : args = NULL;
41 1 : crm_exit(CRM_EX_OSERR);
42 : }
43 :
44 83 : return args;
45 : }
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 : }
58 :
59 0 : free(common_args);
60 0 : }
61 :
62 : GOptionContext *
63 0 : 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 0 : GOptionEntry main_entries[3] = {
69 0 : { "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 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 : "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 : NULL,
92 : N_("FORMAT") },
93 0 : { "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 0 : if (*output_group == NULL) {
100 0 : *output_group = g_option_group_new("output", N_("Output Options:"), N_("Show output help"), NULL, NULL);
101 : }
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 : }
108 :
109 : // main_group is now owned by context, we don't free it here
110 : // cppcheck-suppress memleak
111 0 : return context;
112 : }
113 :
114 : void
115 0 : pcmk__free_arg_context(GOptionContext *context) {
116 0 : if (context == NULL) {
117 0 : return;
118 : }
119 :
120 0 : g_option_context_free(context);
121 : }
122 :
123 : void
124 0 : pcmk__add_main_args(GOptionContext *context, const GOptionEntry entries[])
125 : {
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 0 : pcmk__add_arg_group(GOptionContext *context, const char *name,
133 : const char *header, const char *desc,
134 : const GOptionEntry entries[])
135 : {
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 1 : 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 1 : gchar **split = g_strsplit(str, sub, 0);
156 1 : gchar *retval = g_strjoinv(repl, split);
157 :
158 1 : g_strfreev(split);
159 1 : return retval;
160 : }
161 :
162 : gchar *
163 96 : pcmk__quote_cmdline(gchar **argv)
164 : {
165 96 : GString *gs = NULL;
166 :
167 96 : if (argv == NULL || argv[0] == NULL) {
168 53 : return NULL;
169 : }
170 :
171 43 : gs = g_string_sized_new(100);
172 :
173 150 : for (int i = 0; argv[i] != NULL; i++) {
174 107 : if (i > 0) {
175 : g_string_append_c(gs, ' ');
176 : }
177 :
178 107 : if (strchr(argv[i], ' ') == NULL) {
179 : /* The arg does not contain a space. */
180 105 : g_string_append(gs, argv[i]);
181 2 : } else if (strchr(argv[i], '\'') == NULL) {
182 : /* The arg contains a space, but not a single quote. */
183 1 : 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 "'", 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 1 : gchar *repl = string_replace(argv[i], "'", "\\\'");
204 1 : pcmk__g_strcat(gs, "'", repl, "'", NULL);
205 1 : g_free(repl);
206 : }
207 : }
208 :
209 43 : return g_string_free(gs, FALSE);
210 : }
211 :
212 : gchar **
213 96 : pcmk__cmdline_preproc(char *const *argv, const char *special) {
214 96 : GPtrArray *arr = NULL;
215 96 : bool saw_dash_dash = false;
216 96 : bool copy_option = false;
217 :
218 96 : if (argv == NULL) {
219 1 : return NULL;
220 : }
221 :
222 95 : if (g_get_prgname() == NULL && argv && *argv) {
223 83 : gchar *basename = g_path_get_basename(*argv);
224 :
225 83 : g_set_prgname(basename);
226 83 : g_free(basename);
227 : }
228 :
229 95 : arr = g_ptr_array_new();
230 :
231 306 : 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 211 : if (saw_dash_dash == false && strcmp(argv[i], "--") == 0) {
238 1 : saw_dash_dash = true;
239 : }
240 :
241 211 : if (saw_dash_dash == true) {
242 4 : g_ptr_array_add(arr, g_strdup(argv[i]));
243 2 : continue;
244 : }
245 :
246 209 : if (copy_option == true) {
247 10 : g_ptr_array_add(arr, g_strdup(argv[i]));
248 5 : copy_option = false;
249 5 : 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 204 : if (pcmk__str_eq(argv[i], "-", pcmk__str_casei)) {
256 2 : g_ptr_array_add(arr, g_strdup(argv[i]));
257 1 : continue;
258 : }
259 :
260 : /* "-INFINITY" is almost certainly meant as a string, not as an option
261 : * list
262 : */
263 203 : if (strcmp(argv[i], "-INFINITY") == 0) {
264 2 : g_ptr_array_add(arr, g_strdup(argv[i]));
265 1 : 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 226 : if (g_str_has_prefix(argv[i], "-") && !g_str_has_prefix(argv[i], "--")) {
272 : /* Skip over leading dash */
273 26 : 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 26 : if (*ch != '\0' && *ch >= '1' && *ch <= '9') {
281 3 : bool is_numeric = true;
282 :
283 6 : while (*ch != '\0') {
284 4 : if (!isdigit(*ch)) {
285 1 : is_numeric = false;
286 1 : break;
287 : }
288 :
289 3 : ch++;
290 : }
291 :
292 3 : if (is_numeric) {
293 2 : g_ptr_array_add(arr, g_strdup_printf("%s", argv[i]));
294 2 : 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 1 : ch = argv[i]+1;
300 : }
301 : }
302 :
303 50 : 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 28 : 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 8 : if (*(ch+1) != '\0') {
314 2 : fprintf(stderr, "Deprecated argument format '-%c%s' used.\n", *ch, ch+1);
315 2 : fprintf(stderr, "Please use '-%c %s' instead. "
316 : "Support will be removed in a future release.\n",
317 2 : *ch, ch+1);
318 :
319 2 : g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
320 4 : g_ptr_array_add(arr, g_strdup(ch+1));
321 2 : 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 6 : g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
329 6 : copy_option = true;
330 6 : ch++;
331 : }
332 :
333 : /* This is a regular short argument. Just copy it over. */
334 : } else {
335 20 : g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
336 20 : 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 352 : g_ptr_array_add(arr, g_strdup(argv[i]));
346 : }
347 : }
348 :
349 95 : g_ptr_array_add(arr, NULL);
350 :
351 95 : return (char **) g_ptr_array_free(arr, FALSE);
352 : }
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 : va_list ap;
361 0 : gboolean retval = TRUE;
362 :
363 0 : va_start(ap, format);
364 0 : len = vasprintf(&buf, format, ap);
365 0 : CRM_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 : }
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 : }
|