diff --git a/include/crm/common/cmdline_internal.h b/include/crm/common/cmdline_internal.h index f49f609e96..e32e2bbdaf 100644 --- a/include/crm/common/cmdline_internal.h +++ b/include/crm/common/cmdline_internal.h @@ -1,169 +1,173 @@ /* * Copyright 2019 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef CMDLINE_INTERNAL__H #define CMDLINE_INTERNAL__H #ifdef __cplusplus extern "C" { #endif #include typedef struct { char *summary; char *output_as_descr; gboolean version; gboolean quiet; unsigned int verbosity; char *output_ty; char *output_dest; } pcmk__common_args_t; /*! * \internal * \brief Allocate a new common args object * * \param[in] summary Summary description of tool for man page * * \return Newly allocated common args object * \note This function will immediately exit the program if memory allocation * fails, since the intent is to call it at the very beginning of a * program, before logging has been set up. */ pcmk__common_args_t * pcmk__new_common_args(const char *summary); /*! * \internal * \brief Create and return a GOptionContext containing the command line options * supported by all tools. * * \note Formatted output options will be added unless fmts is NULL. This allows * for using this function in tools that have not yet been converted to * formatted output. It should not be NULL in any tool that calls * pcmk__register_formats() as that function adds its own command line * options. * * \param[in,out] common_args A ::pcmk__common_args_t structure where the * results of handling command options will be written. * \param[in] fmts The help string for which formats are supported. * \param[in,out] output_group A ::GOptionGroup that formatted output related * command line arguments should be added to. * \param[in] param_string A string describing any remaining command line * arguments. */ GOptionContext * pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts, GOptionGroup **output_group, const char *param_string); /*! * \internal * \brief Clean up after pcmk__build_arg_context(). This should be called * instead of ::g_option_context_free at program termination. * * \param[in,out] context Argument context to free */ void pcmk__free_arg_context(GOptionContext *context); /*! * \internal * \brief Add options to the main application options * * \param[in,out] context Argument context to add options to * \param[in] entries Option entries to add * * \note This is simply a convenience wrapper to reduce duplication */ void pcmk__add_main_args(GOptionContext *context, GOptionEntry entries[]); /*! * \internal * \brief Add an option group to an argument context * * \param[in,out] context Argument context to add group to * \param[in] name Option group name (to be used in --help-NAME) * \param[in] header Header for --help-NAME output * \param[in] desc Short description for --help-NAME option * \param[in] entries Array of options in group * * \note This is simply a convenience wrapper to reduce duplication */ void pcmk__add_arg_group(GOptionContext *context, const char *name, const char *header, const char *desc, GOptionEntry entries[]); /*! * \internal * \brief Pre-process command line arguments to preserve compatibility with * getopt behavior. * * getopt and glib have slightly different behavior when it comes to processing * single command line arguments. getopt allows this: -x, while glib will * try to handle like it is additional single letter arguments. glib * prefers -x instead. * * This function scans argv, looking for any single letter command line options * (indicated by the 'special' parameter). When one is found, everything after * that argument to the next whitespace is converted into its own value. Single * letter command line options can come in a group after a single dash, but * this function will expand each group into many arguments. * * Long options and anything after "--" is preserved. The result of this function * can then be passed to ::g_option_context_parse_strv for actual processing. * * In pseudocode, this: * * pcmk__cmdline_preproc(4, ["-XbA", "--blah=foo", "-aF", "-Fval", "--", "--extra", "-args"], "aF") * * Would be turned into this: * * ["-X", "-b", "-A", "--blah=foo", "-a", "F", "-F", "val", "--", "--extra", "-args"] * * This function does not modify argv, and the return value is built of copies * of all the command line arguments. It is up to the caller to free this memory * after use. * + * \note This function calls g_set_prgname assuming it wasn't previously set and + * assuming argv is not NULL. It is not safe to call g_set_prgname more + * than once so clients should not do so after calling this function. + * * \param[in] argv The command line arguments. * \param[in] special Single-letter command line arguments that take a value. * These letters will all have pre-processing applied. */ gchar ** pcmk__cmdline_preproc(char **argv, const char *special); /*! * \internal * \brief Process extra arguments as if they were provided by the user on the * command line. * * \param[in,out] context The command line option processing context. * \param[out] error A place for errors to be collected. * \param[in] format The command line to be processed, potentially with * format specifiers. * \param[in] ... Arguments to be formatted. * * \note The first item in the list of arguments must be the name of the * program, exactly as if the format string were coming from the * command line. Otherwise, the first argument will be ignored. * * \return TRUE if processing succeeded, or FALSE otherwise. If FALSE, error * should be checked and displayed to the user. */ G_GNUC_PRINTF(3, 4) gboolean pcmk__force_args(GOptionContext *context, GError **error, const char *format, ...); #ifdef __cplusplus } #endif #endif diff --git a/lib/common/cmdline.c b/lib/common/cmdline.c index 58f598879a..b5d9b27be1 100644 --- a/lib/common/cmdline.c +++ b/lib/common/cmdline.c @@ -1,267 +1,271 @@ /* * Copyright 2019 the Pacemaker project contributors * * The version control history for this file may have further details. * * This source code is licensed under the GNU Lesser General Public License * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include static gboolean bump_verbosity(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { pcmk__common_args_t *common_args = (pcmk__common_args_t *) data; common_args->verbosity++; return TRUE; } pcmk__common_args_t * pcmk__new_common_args(const char *summary) { pcmk__common_args_t *args = NULL; args = calloc(1, sizeof(pcmk__common_args_t)); if (args == NULL) { crm_exit(crm_errno2exit(-ENOMEM)); } args->summary = strdup(summary); if (args->summary == NULL) { crm_exit(crm_errno2exit(-ENOMEM)); } return args; } static void free_common_args(gpointer data) { pcmk__common_args_t *common_args = (pcmk__common_args_t *) data; free(common_args->summary); free(common_args->output_ty); free(common_args->output_dest); if (common_args->output_as_descr != NULL) { free(common_args->output_as_descr); } free(common_args); } GOptionContext * pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts, GOptionGroup **output_group, const char *param_string) { char *desc = crm_strdup_printf("Report bugs to %s\n", PACKAGE_BUGREPORT); GOptionContext *context; GOptionGroup *main_group; GOptionEntry main_entries[3] = { { "version", '$', 0, G_OPTION_ARG_NONE, &(common_args->version), "Display software version and exit", NULL }, { "verbose", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, bump_verbosity, "Increase debug output (may be specified multiple times)", NULL }, { NULL } }; main_group = g_option_group_new(NULL, "Application Options:", NULL, common_args, free_common_args); g_option_group_add_entries(main_group, main_entries); context = g_option_context_new(param_string); g_option_context_set_summary(context, common_args->summary); g_option_context_set_description(context, desc); g_option_context_set_main_group(context, main_group); if (fmts != NULL) { GOptionEntry output_entries[3] = { { "output-as", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_ty), NULL, "FORMAT" }, { "output-to", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_dest), "Specify file name for output (or \"-\" for stdout)", "DEST" }, { NULL } }; if (*output_group == NULL) { *output_group = g_option_group_new("output", "Output Options:", "Show output help", NULL, NULL); } common_args->output_as_descr = crm_strdup_printf("Specify output format as one of: %s", fmts); output_entries[0].description = common_args->output_as_descr; g_option_group_add_entries(*output_group, output_entries); g_option_context_add_group(context, *output_group); } free(desc); // main_group is now owned by context, we don't free it here // cppcheck-suppress memleak return context; } void pcmk__free_arg_context(GOptionContext *context) { if (context == NULL) { return; } g_option_context_free(context); } void pcmk__add_main_args(GOptionContext *context, GOptionEntry entries[]) { GOptionGroup *main_group = g_option_context_get_main_group(context); g_option_group_add_entries(main_group, entries); } void pcmk__add_arg_group(GOptionContext *context, const char *name, const char *header, const char *desc, GOptionEntry entries[]) { GOptionGroup *group = NULL; group = g_option_group_new(name, header, desc, NULL, NULL); g_option_group_add_entries(group, entries); g_option_context_add_group(context, group); // group is now owned by context, we don't free it here // cppcheck-suppress memleak } gchar ** pcmk__cmdline_preproc(char **argv, const char *special) { gchar **retval = NULL; GPtrArray *arr = NULL; bool saw_dash_dash = false; if (argv == NULL) { return retval; } + if (g_get_prgname() == NULL && argv && *argv) { + g_set_prgname(g_path_get_basename(*argv)); + } + arr = g_ptr_array_new(); for (int i = 0; argv[i] != NULL; i++) { /* If this is the first time we saw "--" in the command line, set * a flag so we know to just copy everything after it over. We also * want to copy the "--" over so whatever actually parses the command * line when we're done knows where arguments end. */ if (saw_dash_dash == false && strcmp(argv[i], "--") == 0) { saw_dash_dash = true; } if (saw_dash_dash == true) { g_ptr_array_add(arr, strdup(argv[i])); continue; } /* This is just a dash by itself. That could indicate stdin/stdout, or * it could be user error. Copy it over and let glib figure it out. */ if (safe_str_eq(argv[i], "-")) { g_ptr_array_add(arr, strdup(argv[i])); continue; } /* This is a short argument, or perhaps several. Iterate over it * and explode them out into individual arguments. */ if (g_str_has_prefix(argv[i], "-") && !g_str_has_prefix(argv[i], "--")) { /* Skip over leading dash */ char *ch = argv[i]+1; while (*ch != '\0') { /* This is a special short argument that takes an option. getopt * allows values to be interspersed with a list of arguments, but * glib does not. Grab both the argument and its value and * separate them into a new argument. */ if (strchr(special, *ch) != NULL) { /* The argument does not occur at the end of this string of * arguments. Take everything through the end as its value. */ if (*(ch+1) != '\0') { g_ptr_array_add(arr, (gpointer) crm_strdup_printf("-%c", *ch)); g_ptr_array_add(arr, strdup(ch+1)); break; /* The argument occurs at the end of this string. Hopefully * whatever comes next in argv is its value. It may not be, * but that is not for us to decide. */ } else { g_ptr_array_add(arr, (gpointer) crm_strdup_printf("-%c", *ch)); ch++; } /* This is a regular short argument. Just copy it over. */ } else { g_ptr_array_add(arr, (gpointer) crm_strdup_printf("-%c", *ch)); ch++; } } /* This is a long argument, or an option, or something else. * Copy it over - everything else is copied, so this keeps it easy for * the caller to know what to do with the memory when it's done. */ } else { g_ptr_array_add(arr, strdup(argv[i])); } } /* Convert the GPtrArray into a gchar **, which the command line parsing * code knows how to deal with. Then we can free the array (but not its * contents). */ retval = calloc(arr->len+1, sizeof(char *)); for (int i = 0; i < arr->len; i++) { retval[i] = (gchar *) g_ptr_array_index(arr, i); } g_ptr_array_free(arr, FALSE); return retval; } G_GNUC_PRINTF(3, 4) gboolean pcmk__force_args(GOptionContext *context, GError **error, const char *format, ...) { int len = 0; char *buf = NULL; gchar **extra_args = NULL; va_list ap; gboolean retval = TRUE; va_start(ap, format); len = vasprintf(&buf, format, ap); CRM_ASSERT(len > 0); va_end(ap); if (!g_shell_parse_argv(buf, NULL, &extra_args, error)) { g_strfreev(extra_args); free(buf); return FALSE; } retval = g_option_context_parse_strv(context, &extra_args, error); g_strfreev(extra_args); free(buf); return retval; }