Coverage Report

Created: 2024-10-12 00:30

/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 "&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
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
}