summaryrefslogtreecommitdiffstats
path: root/lib/common/cmdline.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
commite5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch)
treea6716c9275b4b413f6c9194798b34b91affb3cc7 /lib/common/cmdline.c
parentInitial commit. (diff)
downloadpacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz
pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/common/cmdline.c')
-rw-r--r--lib/common/cmdline.c379
1 files changed, 379 insertions, 0 deletions
diff --git a/lib/common/cmdline.c b/lib/common/cmdline.c
new file mode 100644
index 0000000..08c43f7
--- /dev/null
+++ b/lib/common/cmdline.c
@@ -0,0 +1,379 @@
+/*
+ * Copyright 2019-2022 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.
+ */
+
+#include <crm_internal.h>
+
+#include <ctype.h>
+#include <glib.h>
+
+#include <crm/crm.h>
+#include <crm/common/cmdline_internal.h>
+#include <crm/common/strings_internal.h>
+#include <crm/common/util.h>
+
+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_EX_OSERR);
+ }
+
+ args->summary = strdup(summary);
+ if (args->summary == NULL) {
+ free(args);
+ args = NULL;
+ crm_exit(CRM_EX_OSERR);
+ }
+
+ 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) {
+ GOptionContext *context;
+ GOptionGroup *main_group;
+
+ GOptionEntry main_entries[3] = {
+ { "version", '$', 0, G_OPTION_ARG_NONE, &(common_args->version),
+ N_("Display software version and exit"),
+ NULL },
+ { "verbose", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, bump_verbosity,
+ N_("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,
+ "Report bugs to " PCMK__BUG_URL "\n");
+ 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,
+ N_("FORMAT") },
+ { "output-to", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_dest),
+ N_( "Specify file name for output (or \"-\" for stdout)"), N_("DEST") },
+
+ { NULL }
+ };
+
+ if (*output_group == NULL) {
+ *output_group = g_option_group_new("output", N_("Output Options:"), N_("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);
+ }
+
+ // 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, const 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,
+ const 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
+}
+
+static gchar *
+string_replace(gchar *str, const gchar *sub, const gchar *repl)
+{
+ /* This function just replaces all occurrences of a substring
+ * with some other string. It doesn't handle cases like overlapping,
+ * so don't get clever with it.
+ *
+ * FIXME: When glib >= 2.68 is supported, we can get rid of this
+ * function and use g_string_replace instead.
+ */
+ gchar **split = g_strsplit(str, sub, 0);
+ gchar *retval = g_strjoinv(repl, split);
+
+ g_strfreev(split);
+ return retval;
+}
+
+gchar *
+pcmk__quote_cmdline(gchar **argv)
+{
+ GString *gs = NULL;
+
+ if (argv == NULL || argv[0] == NULL) {
+ return NULL;
+ }
+
+ gs = g_string_sized_new(100);
+
+ for (int i = 0; argv[i] != NULL; i++) {
+ if (i > 0) {
+ g_string_append_c(gs, ' ');
+ }
+
+ if (strchr(argv[i], ' ') == NULL) {
+ /* The arg does not contain a space. */
+ g_string_append(gs, argv[i]);
+ } else if (strchr(argv[i], '\'') == NULL) {
+ /* The arg contains a space, but not a single quote. */
+ pcmk__g_strcat(gs, "'", argv[i], "'", NULL);
+ } else {
+ /* The arg contains both a space and a single quote, which needs to
+ * be replaced with an escaped version. We do this instead of counting
+ * on libxml to handle the escaping for various reasons:
+ *
+ * (1) This keeps the string as valid shell.
+ * (2) We don't want to use XML entities in formats besides XML and HTML.
+ * (3) The string we are feeding to libxml is something like: "a b 'c d' e".
+ * It won't escape the single quotes around 'c d' here because there is
+ * no need to escape quotes inside a different form of quote. If we
+ * change the string to "a b 'c'd' e", we haven't changed anything - it's
+ * still single quotes inside double quotes.
+ *
+ * On the other hand, if we replace the single quote with "&apos;", then
+ * we have introduced an ampersand which libxml will escape. This leaves
+ * us with "&amp;apos;" which is not what we want.
+ *
+ * It's simplest to just escape with a backslash.
+ */
+ gchar *repl = string_replace(argv[i], "'", "\\\'");
+ pcmk__g_strcat(gs, "'", repl, "'", NULL);
+ g_free(repl);
+ }
+ }
+
+ return g_string_free(gs, FALSE);
+}
+
+gchar **
+pcmk__cmdline_preproc(char *const *argv, const char *special) {
+ GPtrArray *arr = NULL;
+ bool saw_dash_dash = false;
+ bool copy_option = false;
+
+ if (argv == NULL) {
+ return NULL;
+ }
+
+ if (g_get_prgname() == NULL && argv && *argv) {
+ gchar *basename = g_path_get_basename(*argv);
+
+ g_set_prgname(basename);
+ g_free(basename);
+ }
+
+ 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, g_strdup(argv[i]));
+ continue;
+ }
+
+ if (copy_option == true) {
+ g_ptr_array_add(arr, g_strdup(argv[i]));
+ copy_option = false;
+ 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 (pcmk__str_eq(argv[i], "-", pcmk__str_casei)) {
+ g_ptr_array_add(arr, g_strdup(argv[i]));
+ continue;
+ }
+
+ /* "-INFINITY" is almost certainly meant as a string, not as an option
+ * list
+ */
+ if (strcmp(argv[i], "-INFINITY") == 0) {
+ g_ptr_array_add(arr, g_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 */
+ const char *ch = argv[i]+1;
+
+ /* This looks like the start of a number, which means it is a negative
+ * number. It's probably the argument to the preceeding option, but
+ * we can't know that here. Copy it over and let whatever handles
+ * arguments next figure it out.
+ */
+ if (*ch != '\0' && *ch >= '1' && *ch <= '9') {
+ bool is_numeric = true;
+
+ while (*ch != '\0') {
+ if (!isdigit(*ch)) {
+ is_numeric = false;
+ break;
+ }
+
+ ch++;
+ }
+
+ if (is_numeric) {
+ g_ptr_array_add(arr, g_strdup_printf("%s", argv[i]));
+ continue;
+ } else {
+ /* This argument wasn't entirely numeric. Reset ch to the
+ * beginning so we can process it one character at a time.
+ */
+ 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 (special != NULL && 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') {
+ fprintf(stderr, "Deprecated argument format '-%c%s' used.\n", *ch, ch+1);
+ fprintf(stderr, "Please use '-%c %s' instead. "
+ "Support will be removed in a future release.\n",
+ *ch, ch+1);
+
+ g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
+ g_ptr_array_add(arr, g_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, g_strdup_printf("-%c", *ch));
+ copy_option = true;
+ ch++;
+ }
+
+ /* This is a regular short argument. Just copy it over. */
+ } else {
+ g_ptr_array_add(arr, g_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, g_strdup(argv[i]));
+ }
+ }
+
+ g_ptr_array_add(arr, NULL);
+
+ return (char **) g_ptr_array_free(arr, FALSE);
+}
+
+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;
+}