1    	/*
2    	 * Copyright 2019-2026 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 <stdbool.h>
14   	
15   	#include <glib.h>
16   	
17   	#include <crm/crm.h>
18   	#include <crm/common/util.h>
19   	
20   	static gboolean
21   	bump_verbosity(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
22   	    pcmk__common_args_t *common_args = (pcmk__common_args_t *) data;
23   	    common_args->verbosity++;
24   	    return TRUE;
25   	}
26   	
27   	pcmk__common_args_t *
28   	pcmk__new_common_args(const char *summary)
29   	{
30   	    pcmk__common_args_t *args = NULL;
31   	
32   	    args = calloc(1, sizeof(pcmk__common_args_t));
(1) Event path: Condition "args == NULL", taking false branch.
33   	    if (args == NULL) {
34   	        crm_exit(CRM_EX_OSERR);
35   	    }
36   	
37   	    // cppcheck-suppress nullPointerOutOfMemory
38   	    args->summary = strdup(summary);
39   	    // cppcheck-suppress nullPointerOutOfMemory
(2) Event path: Condition "args->summary == NULL", taking true branch.
40   	    if (args->summary == NULL) {
CID (unavailable; MK=80a7298652e63514aa9d5dc6e592e959) (#1 of 1): Inconsistent C union access (INCONSISTENT_UNION_ACCESS):
(3) Event assign_union_field: The union field "in" of "_pp" is written.
(4) Event inconsistent_union_field_access: In "_pp.out", the union field used: "out" is inconsistent with the field most recently stored: "in".
41   	        g_clear_pointer(&args, free);
42   	        crm_exit(CRM_EX_OSERR);
43   	    }
44   	
45   	    return args;
46   	}
47   	
48   	static void
49   	free_common_args(gpointer data) {
50   	    pcmk__common_args_t *common_args = (pcmk__common_args_t *) data;
51   	
52   	    free(common_args->summary);
53   	    free(common_args->output_ty);
54   	    free(common_args->output_dest);
55   	
56   	    if (common_args->output_as_descr != NULL) {
57   	        free(common_args->output_as_descr);
58   	    }
59   	
60   	    free(common_args);
61   	}
62   	
63   	GOptionContext *
64   	pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts,
65   	                        GOptionGroup **output_group, const char *param_string) {
66   	    GOptionContext *context;
67   	    GOptionGroup *main_group;
68   	
69   	    GOptionEntry main_entries[3] = {
70   	        { "version", '$', 0, G_OPTION_ARG_NONE, &(common_args->version),
71   	          N_("Display software version and exit"),
72   	          NULL },
73   	        { "verbose", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, bump_verbosity,
74   	          N_("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,
86   	                                     "Report bugs to " PCMK__BUG_URL "\n");
87   	    g_option_context_set_main_group(context, main_group);
88   	
89   	    if (fmts != NULL) {
90   	        GOptionEntry output_entries[3] = {
91   	            { "output-as", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_ty),
92   	              NULL,
93   	              N_("FORMAT") },
94   	            { "output-to", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_dest),
95   	              N_( "Specify file name for output (or \"-\" for stdout)"), N_("DEST") },
96   	
97   	            { NULL }
98   	        };
99   	
100  	        if (*output_group == NULL) {
101  	            *output_group = g_option_group_new("output", N_("Output Options:"), N_("Show output help"), NULL, NULL);
102  	        }
103  	
104  	        common_args->output_as_descr =
105  	            pcmk__assert_asprintf("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  	
115  	void
116  	pcmk__free_arg_context(GOptionContext *context) {
117  	    if (context == NULL) {
118  	        return;
119  	    }
120  	
121  	    g_option_context_free(context);
122  	}
123  	
124  	void
125  	pcmk__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  	
132  	void
133  	pcmk__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  	
145  	gchar *
146  	pcmk__quote_cmdline(gchar **argv)
147  	{
148  	    GString *cmdline = NULL;
149  	
150  	    if (argv == NULL) {
151  	        return NULL;
152  	    }
153  	
154  	    for (int i = 0; argv[i] != NULL; i++) {
155  	        gint argc = 0;
156  	
157  	        /* Quote the argument if it's unparsable as-is (empty, all whitespace,
158  	         * or having mismatched quotes), or if it contains more than one token
159  	         */
160  	        if (!g_shell_parse_argv(argv[i], &argc, NULL, NULL) || (argc > 1)) {
161  	            gchar *quoted = g_shell_quote(argv[i]);
162  	
163  	            pcmk__add_word(&cmdline, 128, quoted);
164  	            g_free(quoted);
165  	
166  	        } else {
167  	            pcmk__add_word(&cmdline, 128, argv[i]);
168  	        }
169  	    }
170  	
171  	    if (cmdline == NULL) {
172  	        return NULL;
173  	    }
174  	    return g_string_free(cmdline, FALSE);
175  	}
176  	
177  	gchar **
178  	pcmk__cmdline_preproc(char *const *argv, const char *special) {
179  	    GPtrArray *arr = NULL;
180  	    bool saw_dash_dash = false;
181  	    bool copy_option = false;
182  	
183  	    if (argv == NULL) {
184  	        return NULL;
185  	    }
186  	
187  	    if (g_get_prgname() == NULL && argv && *argv) {
188  	        gchar *basename = g_path_get_basename(*argv);
189  	
190  	        g_set_prgname(basename);
191  	        g_free(basename);
192  	    }
193  	
194  	    arr = g_ptr_array_new();
195  	
196  	    for (int i = 0; argv[i] != NULL; i++) {
197  	        /* If this is the first time we saw "--" in the command line, set
198  	         * a flag so we know to just copy everything after it over.  We also
199  	         * want to copy the "--" over so whatever actually parses the command
200  	         * line when we're done knows where arguments end.
201  	         */
202  	        if (saw_dash_dash == false && strcmp(argv[i], "--") == 0) {
203  	            saw_dash_dash = true;
204  	        }
205  	
206  	        if (saw_dash_dash == true) {
207  	            g_ptr_array_add(arr, g_strdup(argv[i]));
208  	            continue;
209  	        }
210  	
211  	        if (copy_option == true) {
212  	            g_ptr_array_add(arr, g_strdup(argv[i]));
213  	            copy_option = false;
214  	            continue;
215  	        }
216  	
217  	        /* This is just a dash by itself.  That could indicate stdin/stdout, or
218  	         * it could be user error.  Copy it over and let glib figure it out.
219  	         */
220  	        if (pcmk__str_eq(argv[i], "-", pcmk__str_casei)) {
221  	            g_ptr_array_add(arr, g_strdup(argv[i]));
222  	            continue;
223  	        }
224  	
225  	        /* "-INFINITY" is almost certainly meant as a string, not as an option
226  	         * list
227  	         */
228  	        if (strcmp(argv[i], "-INFINITY") == 0) {
229  	            g_ptr_array_add(arr, g_strdup(argv[i]));
230  	            continue;
231  	        }
232  	
233  	        /* This is a short argument, or perhaps several.  Iterate over it
234  	         * and explode them out into individual arguments.
235  	         */
236  	        if (g_str_has_prefix(argv[i], "-") && !g_str_has_prefix(argv[i], "--")) {
237  	            /* Skip over leading dash */
238  	            const char *ch = argv[i]+1;
239  	
240  	            /* This looks like the start of a number, which means it is a negative
241  	             * number.  It's probably the argument to the preceeding option, but
242  	             * we can't know that here.  Copy it over and let whatever handles
243  	             * arguments next figure it out.
244  	             */
245  	            if (*ch != '\0' && *ch >= '1' && *ch <= '9') {
246  	                bool is_numeric = true;
247  	
248  	                while (*ch != '\0') {
249  	                    if (!isdigit(*ch)) {
250  	                        is_numeric = false;
251  	                        break;
252  	                    }
253  	
254  	                    ch++;
255  	                }
256  	
257  	                if (is_numeric) {
258  	                    g_ptr_array_add(arr, g_strdup_printf("%s", argv[i]));
259  	                    continue;
260  	                } else {
261  	                    /* This argument wasn't entirely numeric.  Reset ch to the
262  	                     * beginning so we can process it one character at a time.
263  	                     */
264  	                    ch = argv[i]+1;
265  	                }
266  	            }
267  	
268  	            while (*ch != '\0') {
269  	                /* This is a special short argument that takes an option.  getopt
270  	                 * allows values to be interspersed with a list of arguments, but
271  	                 * glib does not.  Grab both the argument and its value and
272  	                 * separate them into a new argument.
273  	                 */
274  	                if (special != NULL && strchr(special, *ch) != NULL) {
275  	                    /* The argument does not occur at the end of this string of
276  	                     * arguments.  Take everything through the end as its value.
277  	                     */
278  	                    if (*(ch+1) != '\0') {
279  	                        fprintf(stderr, "Deprecated argument format '-%c%s' used.\n", *ch, ch+1);
280  	                        fprintf(stderr, "Please use '-%c %s' instead.  "
281  	                                        "Support will be removed in a future release.\n",
282  	                                *ch, ch+1);
283  	
284  	                        g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
285  	                        g_ptr_array_add(arr, g_strdup(ch+1));
286  	                        break;
287  	
288  	                    /* The argument occurs at the end of this string.  Hopefully
289  	                     * whatever comes next in argv is its value.  It may not be,
290  	                     * but that is not for us to decide.
291  	                     */
292  	                    } else {
293  	                        g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
294  	                        copy_option = true;
295  	                        ch++;
296  	                    }
297  	
298  	                /* This is a regular short argument.  Just copy it over. */
299  	                } else {
300  	                    g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
301  	                    ch++;
302  	                }
303  	            }
304  	
305  	        /* This is a long argument, or an option, or something else.
306  	         * Copy it over - everything else is copied, so this keeps it easy for
307  	         * the caller to know what to do with the memory when it's done.
308  	         */
309  	        } else {
310  	            g_ptr_array_add(arr, g_strdup(argv[i]));
311  	        }
312  	    }
313  	
314  	    g_ptr_array_add(arr, NULL);
315  	
316  	    return (char **) g_ptr_array_free(arr, FALSE);
317  	}
318