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