root/lib/common/cmdline.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. bump_verbosity
  2. pcmk__new_common_args
  3. free_common_args
  4. pcmk__build_arg_context
  5. pcmk__free_arg_context
  6. pcmk__add_main_args
  7. pcmk__add_arg_group
  8. string_replace
  9. pcmk__quote_cmdline
  10. pcmk__cmdline_preproc
  11. G_GNUC_PRINTF

   1 /*
   2  * Copyright 2019-2024 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
  21 bump_verbosity(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
     /* [previous][next][first][last][top][bottom][index][help] */
  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)
     /* [previous][next][first][last][top][bottom][index][help] */
  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_EX_OSERR);
  35     }
  36 
  37     args->summary = strdup(summary);
  38     if (args->summary == NULL) {
  39         free(args);
  40         args = NULL;
  41         crm_exit(CRM_EX_OSERR);
  42     }
  43 
  44     return args;
  45 }
  46 
  47 static void
  48 free_common_args(gpointer data) {
     /* [previous][next][first][last][top][bottom][index][help] */
  49     pcmk__common_args_t *common_args = (pcmk__common_args_t *) data;
  50 
  51     free(common_args->summary);
  52     free(common_args->output_ty);
  53     free(common_args->output_dest);
  54 
  55     if (common_args->output_as_descr != NULL) {
  56         free(common_args->output_as_descr);
  57     }
  58 
  59     free(common_args);
  60 }
  61 
  62 GOptionContext *
  63 pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts,
     /* [previous][next][first][last][top][bottom][index][help] */
  64                         GOptionGroup **output_group, const char *param_string) {
  65     GOptionContext *context;
  66     GOptionGroup *main_group;
  67 
  68     GOptionEntry main_entries[3] = {
  69         { "version", '$', 0, G_OPTION_ARG_NONE, &(common_args->version),
  70           N_("Display software version and exit"),
  71           NULL },
  72         { "verbose", 'V', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, bump_verbosity,
  73           N_("Increase debug output (may be specified multiple times)"),
  74           NULL },
  75 
  76         { NULL }
  77     };
  78 
  79     main_group = g_option_group_new(NULL, "Application Options:", NULL, common_args, free_common_args);
  80     g_option_group_add_entries(main_group, main_entries);
  81 
  82     context = g_option_context_new(param_string);
  83     g_option_context_set_summary(context, common_args->summary);
  84     g_option_context_set_description(context,
  85                                      "Report bugs to " PCMK__BUG_URL "\n");
  86     g_option_context_set_main_group(context, main_group);
  87 
  88     if (fmts != NULL) {
  89         GOptionEntry output_entries[3] = {
  90             { "output-as", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_ty),
  91               NULL,
  92               N_("FORMAT") },
  93             { "output-to", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_dest),
  94               N_( "Specify file name for output (or \"-\" for stdout)"), N_("DEST") },
  95 
  96             { NULL }
  97         };
  98 
  99         if (*output_group == NULL) {
 100             *output_group = g_option_group_new("output", N_("Output Options:"), N_("Show output help"), NULL, NULL);
 101         }
 102 
 103         common_args->output_as_descr = crm_strdup_printf("Specify output format as one of: %s", fmts);
 104         output_entries[0].description = common_args->output_as_descr;
 105         g_option_group_add_entries(*output_group, output_entries);
 106         g_option_context_add_group(context, *output_group);
 107     }
 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
 115 pcmk__free_arg_context(GOptionContext *context) {
     /* [previous][next][first][last][top][bottom][index][help] */
 116     if (context == NULL) {
 117         return;
 118     }
 119 
 120     g_option_context_free(context);
 121 }
 122 
 123 void
 124 pcmk__add_main_args(GOptionContext *context, const GOptionEntry entries[])
     /* [previous][next][first][last][top][bottom][index][help] */
 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
 132 pcmk__add_arg_group(GOptionContext *context, const char *name,
     /* [previous][next][first][last][top][bottom][index][help] */
 133                     const char *header, const char *desc,
 134                     const 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 static gchar *
 146 string_replace(gchar *str, const gchar *sub, const gchar *repl)
     /* [previous][next][first][last][top][bottom][index][help] */
 147 {
 148     /* This function just replaces all occurrences of a substring
 149      * with some other string.  It doesn't handle cases like overlapping,
 150      * so don't get clever with it.
 151      *
 152      * FIXME: When glib >= 2.68 is supported, we can get rid of this
 153      * function and use g_string_replace instead.
 154      */
 155     gchar **split = g_strsplit(str, sub, 0);
 156     gchar *retval = g_strjoinv(repl, split);
 157 
 158     g_strfreev(split);
 159     return retval;
 160 }
 161 
 162 gchar *
 163 pcmk__quote_cmdline(gchar **argv)
     /* [previous][next][first][last][top][bottom][index][help] */
 164 {
 165     GString *gs = NULL;
 166 
 167     if (argv == NULL || argv[0] == NULL) {
 168         return NULL;
 169     }
 170 
 171     gs = g_string_sized_new(100);
 172 
 173     for (int i = 0; argv[i] != NULL; i++) {
 174         if (i > 0) {
 175             g_string_append_c(gs, ' ');
 176         }
 177 
 178         if (strchr(argv[i], ' ') == NULL) {
 179             /* The arg does not contain a space. */
 180             g_string_append(gs, argv[i]);
 181         } else if (strchr(argv[i], '\'') == NULL) {
 182             /* The arg contains a space, but not a single quote. */
 183             pcmk__g_strcat(gs, "'", argv[i], "'", NULL);
 184         } else {
 185             /* The arg contains both a space and a single quote, which needs to
 186              * be replaced with an escaped version.  We do this instead of counting
 187              * on libxml to handle the escaping for various reasons:
 188              *
 189              * (1) This keeps the string as valid shell.
 190              * (2) We don't want to use XML entities in formats besides XML and HTML.
 191              * (3) The string we are feeding to libxml is something like:  "a b 'c d' e".
 192              *     It won't escape the single quotes around 'c d' here because there is
 193              *     no need to escape quotes inside a different form of quote.  If we
 194              *     change the string to "a b 'c'd' e", we haven't changed anything - it's
 195              *     still single quotes inside double quotes.
 196              *
 197              *     On the other hand, if we replace the single quote with "&apos;", then
 198              *     we have introduced an ampersand which libxml will escape.  This leaves
 199              *     us with "&amp;apos;" which is not what we want.
 200              *
 201              * It's simplest to just escape with a backslash.
 202              */
 203             gchar *repl = string_replace(argv[i], "'", "\\\'");
 204             pcmk__g_strcat(gs, "'", repl, "'", NULL);
 205             g_free(repl);
 206         }
 207     }
 208 
 209     return g_string_free(gs, FALSE);
 210 }
 211 
 212 gchar **
 213 pcmk__cmdline_preproc(char *const *argv, const char *special) {
     /* [previous][next][first][last][top][bottom][index][help] */
 214     GPtrArray *arr = NULL;
 215     bool saw_dash_dash = false;
 216     bool copy_option = false;
 217 
 218     if (argv == NULL) {
 219         return NULL;
 220     }
 221 
 222     if (g_get_prgname() == NULL && argv && *argv) {
 223         gchar *basename = g_path_get_basename(*argv);
 224 
 225         g_set_prgname(basename);
 226         g_free(basename);
 227     }
 228 
 229     arr = g_ptr_array_new();
 230 
 231     for (int i = 0; argv[i] != NULL; i++) {
 232         /* If this is the first time we saw "--" in the command line, set
 233          * a flag so we know to just copy everything after it over.  We also
 234          * want to copy the "--" over so whatever actually parses the command
 235          * line when we're done knows where arguments end.
 236          */
 237         if (saw_dash_dash == false && strcmp(argv[i], "--") == 0) {
 238             saw_dash_dash = true;
 239         }
 240 
 241         if (saw_dash_dash == true) {
 242             g_ptr_array_add(arr, g_strdup(argv[i]));
 243             continue;
 244         }
 245 
 246         if (copy_option == true) {
 247             g_ptr_array_add(arr, g_strdup(argv[i]));
 248             copy_option = false;
 249             continue;
 250         }
 251 
 252         /* This is just a dash by itself.  That could indicate stdin/stdout, or
 253          * it could be user error.  Copy it over and let glib figure it out.
 254          */
 255         if (pcmk__str_eq(argv[i], "-", pcmk__str_casei)) {
 256             g_ptr_array_add(arr, g_strdup(argv[i]));
 257             continue;
 258         }
 259 
 260         /* "-INFINITY" is almost certainly meant as a string, not as an option
 261          * list
 262          */
 263         if (strcmp(argv[i], "-INFINITY") == 0) {
 264             g_ptr_array_add(arr, g_strdup(argv[i]));
 265             continue;
 266         }
 267 
 268         /* This is a short argument, or perhaps several.  Iterate over it
 269          * and explode them out into individual arguments.
 270          */
 271         if (g_str_has_prefix(argv[i], "-") && !g_str_has_prefix(argv[i], "--")) {
 272             /* Skip over leading dash */
 273             const char *ch = argv[i]+1;
 274 
 275             /* This looks like the start of a number, which means it is a negative
 276              * number.  It's probably the argument to the preceeding option, but
 277              * we can't know that here.  Copy it over and let whatever handles
 278              * arguments next figure it out.
 279              */
 280             if (*ch != '\0' && *ch >= '1' && *ch <= '9') {
 281                 bool is_numeric = true;
 282 
 283                 while (*ch != '\0') {
 284                     if (!isdigit(*ch)) {
 285                         is_numeric = false;
 286                         break;
 287                     }
 288 
 289                     ch++;
 290                 }
 291 
 292                 if (is_numeric) {
 293                     g_ptr_array_add(arr, g_strdup_printf("%s", argv[i]));
 294                     continue;
 295                 } else {
 296                     /* This argument wasn't entirely numeric.  Reset ch to the
 297                      * beginning so we can process it one character at a time.
 298                      */
 299                     ch = argv[i]+1;
 300                 }
 301             }
 302 
 303             while (*ch != '\0') {
 304                 /* This is a special short argument that takes an option.  getopt
 305                  * allows values to be interspersed with a list of arguments, but
 306                  * glib does not.  Grab both the argument and its value and
 307                  * separate them into a new argument.
 308                  */
 309                 if (special != NULL && strchr(special, *ch) != NULL) {
 310                     /* The argument does not occur at the end of this string of
 311                      * arguments.  Take everything through the end as its value.
 312                      */
 313                     if (*(ch+1) != '\0') {
 314                         fprintf(stderr, "Deprecated argument format '-%c%s' used.\n", *ch, ch+1);
 315                         fprintf(stderr, "Please use '-%c %s' instead.  "
 316                                         "Support will be removed in a future release.\n",
 317                                 *ch, ch+1);
 318 
 319                         g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
 320                         g_ptr_array_add(arr, g_strdup(ch+1));
 321                         break;
 322 
 323                     /* The argument occurs at the end of this string.  Hopefully
 324                      * whatever comes next in argv is its value.  It may not be,
 325                      * but that is not for us to decide.
 326                      */
 327                     } else {
 328                         g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
 329                         copy_option = true;
 330                         ch++;
 331                     }
 332 
 333                 /* This is a regular short argument.  Just copy it over. */
 334                 } else {
 335                     g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
 336                     ch++;
 337                 }
 338             }
 339 
 340         /* This is a long argument, or an option, or something else.
 341          * Copy it over - everything else is copied, so this keeps it easy for
 342          * the caller to know what to do with the memory when it's done.
 343          */
 344         } else {
 345             g_ptr_array_add(arr, g_strdup(argv[i]));
 346         }
 347     }
 348 
 349     g_ptr_array_add(arr, NULL);
 350 
 351     return (char **) g_ptr_array_free(arr, FALSE);
 352 }
 353 
 354 G_GNUC_PRINTF(3, 4)
     /* [previous][next][first][last][top][bottom][index][help] */
 355 gboolean
 356 pcmk__force_args(GOptionContext *context, GError **error, const char *format, ...) {
 357     int len = 0;
 358     char *buf = NULL;
 359     gchar **extra_args = NULL;
 360     va_list ap;
 361     gboolean retval = TRUE;
 362 
 363     va_start(ap, format);
 364     len = vasprintf(&buf, format, ap);
 365     pcmk__assert(len > 0);
 366     va_end(ap);
 367 
 368     if (!g_shell_parse_argv(buf, NULL, &extra_args, error)) {
 369         g_strfreev(extra_args);
 370         free(buf);
 371         return FALSE;
 372     }
 373 
 374     retval = g_option_context_parse_strv(context, &extra_args, error);
 375 
 376     g_strfreev(extra_args);
 377     free(buf);
 378     return retval;
 379 }

/* [previous][next][first][last][top][bottom][index][help] */