1 /*
2  * Copyright 2019-2021 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
bump_verbosity(const gchar * option_name,const gchar * optarg,gpointer data,GError ** error)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 *
pcmk__new_common_args(const char * summary)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));
33     if (args == NULL) {
34         crm_exit(crm_errno2exit(-ENOMEM));
35     }
36 
37     args->summary = strdup(summary);
38     if (args->summary == NULL) {
39         crm_exit(crm_errno2exit(-ENOMEM));
40     }
41 
42     return args;
43 }
44 
45 static void
free_common_args(gpointer data)46 free_common_args(gpointer data) {
47     pcmk__common_args_t *common_args = (pcmk__common_args_t *) data;
48 
49     free(common_args->summary);
50     free(common_args->output_ty);
51     free(common_args->output_dest);
52 
53     if (common_args->output_as_descr != NULL) {
54         free(common_args->output_as_descr);
55     }
56 
57     free(common_args);
58 }
59 
60 GOptionContext *
pcmk__build_arg_context(pcmk__common_args_t * common_args,const char * fmts,GOptionGroup ** output_group,const char * param_string)61 pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts,
62                         GOptionGroup **output_group, const char *param_string) {
63     char *desc = crm_strdup_printf("Report bugs to %s\n", PACKAGE_BUGREPORT);
64     GOptionContext *context;
65     GOptionGroup *main_group;
66 
67     GOptionEntry main_entries[3] = {
68         { "version", '$', 0, G_OPTION_ARG_NONE, &(common_args->version),
69           "Display software version and exit",
70           NULL },
71         { "verbose", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, bump_verbosity,
72           "Increase debug output (may be specified multiple times)",
73           NULL },
74 
75         { NULL }
76     };
77 
78     main_group = g_option_group_new(NULL, "Application Options:", NULL, common_args, free_common_args);
79     g_option_group_add_entries(main_group, main_entries);
80 
81     context = g_option_context_new(param_string);
82     g_option_context_set_summary(context, common_args->summary);
83     g_option_context_set_description(context, desc);
84     g_option_context_set_main_group(context, main_group);
85 
86     if (fmts != NULL) {
87         GOptionEntry output_entries[3] = {
88             { "output-as", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_ty),
89               NULL,
90               "FORMAT" },
91             { "output-to", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_dest),
92               "Specify file name for output (or \"-\" for stdout)", "DEST" },
93 
94             { NULL }
95         };
96 
97         if (*output_group == NULL) {
98             *output_group = g_option_group_new("output", "Output Options:", "Show output help", NULL, NULL);
99         }
100 
101         common_args->output_as_descr = crm_strdup_printf("Specify output format as one of: %s", fmts);
102         output_entries[0].description = common_args->output_as_descr;
103         g_option_group_add_entries(*output_group, output_entries);
104         g_option_context_add_group(context, *output_group);
105     }
106 
107     free(desc);
108 
109     // main_group is now owned by context, we don't free it here
110     // cppcheck-suppress memleak
111     return context;
112 }
113 
114 void
pcmk__free_arg_context(GOptionContext * context)115 pcmk__free_arg_context(GOptionContext *context) {
116     if (context == NULL) {
117         return;
118     }
119 
120     g_option_context_free(context);
121 }
122 
123 void
pcmk__add_main_args(GOptionContext * context,GOptionEntry entries[])124 pcmk__add_main_args(GOptionContext *context, GOptionEntry entries[])
125 {
126     GOptionGroup *main_group = g_option_context_get_main_group(context);
127 
128     g_option_group_add_entries(main_group, entries);
129 }
130 
131 void
pcmk__add_arg_group(GOptionContext * context,const char * name,const char * header,const char * desc,GOptionEntry entries[])132 pcmk__add_arg_group(GOptionContext *context, const char *name,
133                     const char *header, const char *desc,
134                     GOptionEntry entries[])
135 {
136     GOptionGroup *group = NULL;
137 
138     group = g_option_group_new(name, header, desc, NULL, NULL);
139     g_option_group_add_entries(group, entries);
140     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 }
144 
145 gchar **
pcmk__cmdline_preproc(char ** argv,const char * special)146 pcmk__cmdline_preproc(char **argv, const char *special) {
147     GPtrArray *arr = NULL;
148     bool saw_dash_dash = false;
149     bool copy_option = false;
150 
151     if (argv == NULL) {
152         return NULL;
153     }
154 
155     if (g_get_prgname() == NULL && argv && *argv) {
156         gchar *basename = g_path_get_basename(*argv);
157 
158         g_set_prgname(basename);
159         g_free(basename);
160     }
161 
162     arr = g_ptr_array_new();
163 
164     for (int i = 0; argv[i] != NULL; i++) {
165         /* If this is the first time we saw "--" in the command line, set
166          * a flag so we know to just copy everything after it over.  We also
167          * want to copy the "--" over so whatever actually parses the command
168          * line when we're done knows where arguments end.
169          */
170         if (saw_dash_dash == false && strcmp(argv[i], "--") == 0) {
171             saw_dash_dash = true;
172         }
173 
174         if (saw_dash_dash == true) {
175             g_ptr_array_add(arr, g_strdup(argv[i]));
176             continue;
177         }
178 
179         if (copy_option == true) {
180             g_ptr_array_add(arr, g_strdup(argv[i]));
181             copy_option = false;
182             continue;
183         }
184 
185         /* This is just a dash by itself.  That could indicate stdin/stdout, or
186          * it could be user error.  Copy it over and let glib figure it out.
187          */
188         if (pcmk__str_eq(argv[i], "-", pcmk__str_casei)) {
189             g_ptr_array_add(arr, g_strdup(argv[i]));
190             continue;
191         }
192 
193         /* This is a short argument, or perhaps several.  Iterate over it
194          * and explode them out into individual arguments.
195          */
196         if (g_str_has_prefix(argv[i], "-") && !g_str_has_prefix(argv[i], "--")) {
197             /* Skip over leading dash */
198             char *ch = argv[i]+1;
199 
200             /* This looks like the start of a number, which means it is a negative
201              * number.  It's probably the argument to the preceeding option, but
202              * we can't know that here.  Copy it over and let whatever handles
203              * arguments next figure it out.
204              */
205             if (*ch != '\0' && *ch >= '1' && *ch <= '9') {
206                 bool is_numeric = true;
207 
208                 while (*ch != '\0') {
209                     if (!isdigit(*ch)) {
210                         is_numeric = false;
211                         break;
212                     }
213 
214                     ch++;
215                 }
216 
217                 if (is_numeric) {
218                     g_ptr_array_add(arr, g_strdup_printf("%s", argv[i]));
219                     continue;
220                 } else {
221                     /* This argument wasn't entirely numeric.  Reset ch to the
222                      * beginning so we can process it one character at a time.
223                      */
224                     ch = argv[i]+1;
225                 }
226             }
227 
228             while (*ch != '\0') {
229                 /* This is a special short argument that takes an option.  getopt
230                  * allows values to be interspersed with a list of arguments, but
231                  * glib does not.  Grab both the argument and its value and
232                  * separate them into a new argument.
233                  */
234                 if (special != NULL && strchr(special, *ch) != NULL) {
235                     /* The argument does not occur at the end of this string of
236                      * arguments.  Take everything through the end as its value.
237                      */
238                     if (*(ch+1) != '\0') {
239                         g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
240                         g_ptr_array_add(arr, g_strdup(ch+1));
241                         break;
242 
243                     /* The argument occurs at the end of this string.  Hopefully
244                      * whatever comes next in argv is its value.  It may not be,
245                      * but that is not for us to decide.
246                      */
247                     } else {
248                         g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
249                         copy_option = true;
250                         ch++;
251                     }
252 
253                 /* This is a regular short argument.  Just copy it over. */
254                 } else {
255                     g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
256                     ch++;
257                 }
258             }
259 
260         /* This is a long argument, or an option, or something else.
261          * Copy it over - everything else is copied, so this keeps it easy for
262          * the caller to know what to do with the memory when it's done.
263          */
264         } else {
265             g_ptr_array_add(arr, g_strdup(argv[i]));
266         }
267     }
268 
269     g_ptr_array_add(arr, NULL);
270 
271     return (char **) g_ptr_array_free(arr, FALSE);
272 }
273 
274 G_GNUC_PRINTF(3, 4)
275 gboolean
pcmk__force_args(GOptionContext * context,GError ** error,const char * format,...)276 pcmk__force_args(GOptionContext *context, GError **error, const char *format, ...) {
277     int len = 0;
278     char *buf = NULL;
279     gchar **extra_args = NULL;
280     va_list ap;
281     gboolean retval = TRUE;
282 
283     va_start(ap, format);
284     len = vasprintf(&buf, format, ap);
285     CRM_ASSERT(len > 0);
286     va_end(ap);
287 
288     if (!g_shell_parse_argv(buf, NULL, &extra_args, error)) {
289         g_strfreev(extra_args);
290         free(buf);
291         return FALSE;
292     }
293 
294     retval = g_option_context_parse_strv(context, &extra_args, error);
295 
296     g_strfreev(extra_args);
297     free(buf);
298     return retval;
299 }
300