LCOV - code coverage report
Current view: top level - common - cmdline.c (source / functions) Hit Total Coverage
Test: Pacemaker code coverage Lines: 88 152 57.9 %
Date: 2024-05-07 11:09:47 Functions: 4 11 36.4 %

          Line data    Source code
       1             : /*
       2             :  * Copyright 2019-2022 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           0 : bump_verbosity(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
      22           0 :     pcmk__common_args_t *common_args = (pcmk__common_args_t *) data;
      23           0 :     common_args->verbosity++;
      24           0 :     return TRUE;
      25             : }
      26             : 
      27             : pcmk__common_args_t *
      28          85 : pcmk__new_common_args(const char *summary)
      29             : {
      30          85 :     pcmk__common_args_t *args = NULL;
      31             : 
      32          85 :     args = calloc(1, sizeof(pcmk__common_args_t));
      33          85 :     if (args == NULL) {
      34           1 :         crm_exit(CRM_EX_OSERR);
      35             :     }
      36             : 
      37          84 :     args->summary = strdup(summary);
      38          84 :     if (args->summary == NULL) {
      39           1 :         free(args);
      40           1 :         args = NULL;
      41           1 :         crm_exit(CRM_EX_OSERR);
      42             :     }
      43             : 
      44          83 :     return args;
      45             : }
      46             : 
      47             : static void
      48           0 : free_common_args(gpointer data) {
      49           0 :     pcmk__common_args_t *common_args = (pcmk__common_args_t *) data;
      50             : 
      51           0 :     free(common_args->summary);
      52           0 :     free(common_args->output_ty);
      53           0 :     free(common_args->output_dest);
      54             : 
      55           0 :     if (common_args->output_as_descr != NULL) {
      56           0 :         free(common_args->output_as_descr);
      57             :     }
      58             : 
      59           0 :     free(common_args);
      60           0 : }
      61             : 
      62             : GOptionContext *
      63           0 : pcmk__build_arg_context(pcmk__common_args_t *common_args, const char *fmts,
      64             :                         GOptionGroup **output_group, const char *param_string) {
      65             :     GOptionContext *context;
      66             :     GOptionGroup *main_group;
      67             : 
      68           0 :     GOptionEntry main_entries[3] = {
      69           0 :         { "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           0 :     main_group = g_option_group_new(NULL, "Application Options:", NULL, common_args, free_common_args);
      80           0 :     g_option_group_add_entries(main_group, main_entries);
      81             : 
      82           0 :     context = g_option_context_new(param_string);
      83           0 :     g_option_context_set_summary(context, common_args->summary);
      84           0 :     g_option_context_set_description(context,
      85             :                                      "Report bugs to " PCMK__BUG_URL "\n");
      86           0 :     g_option_context_set_main_group(context, main_group);
      87             : 
      88           0 :     if (fmts != NULL) {
      89           0 :         GOptionEntry output_entries[3] = {
      90           0 :             { "output-as", 0, 0, G_OPTION_ARG_STRING, &(common_args->output_ty),
      91             :               NULL,
      92             :               N_("FORMAT") },
      93           0 :             { "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           0 :         if (*output_group == NULL) {
     100           0 :             *output_group = g_option_group_new("output", N_("Output Options:"), N_("Show output help"), NULL, NULL);
     101             :         }
     102             : 
     103           0 :         common_args->output_as_descr = crm_strdup_printf("Specify output format as one of: %s", fmts);
     104           0 :         output_entries[0].description = common_args->output_as_descr;
     105           0 :         g_option_group_add_entries(*output_group, output_entries);
     106           0 :         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           0 :     return context;
     112             : }
     113             : 
     114             : void
     115           0 : pcmk__free_arg_context(GOptionContext *context) {
     116           0 :     if (context == NULL) {
     117           0 :         return;
     118             :     }
     119             : 
     120           0 :     g_option_context_free(context);
     121             : }
     122             : 
     123             : void
     124           0 : pcmk__add_main_args(GOptionContext *context, const GOptionEntry entries[])
     125             : {
     126           0 :     GOptionGroup *main_group = g_option_context_get_main_group(context);
     127             : 
     128           0 :     g_option_group_add_entries(main_group, entries);
     129           0 : }
     130             : 
     131             : void
     132           0 : pcmk__add_arg_group(GOptionContext *context, const char *name,
     133             :                     const char *header, const char *desc,
     134             :                     const GOptionEntry entries[])
     135             : {
     136           0 :     GOptionGroup *group = NULL;
     137             : 
     138           0 :     group = g_option_group_new(name, header, desc, NULL, NULL);
     139           0 :     g_option_group_add_entries(group, entries);
     140           0 :     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           0 : }
     144             : 
     145             : static gchar *
     146           1 : string_replace(gchar *str, const gchar *sub, const gchar *repl)
     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           1 :     gchar **split = g_strsplit(str, sub, 0);
     156           1 :     gchar *retval = g_strjoinv(repl, split);
     157             : 
     158           1 :     g_strfreev(split);
     159           1 :     return retval;
     160             : }
     161             : 
     162             : gchar *
     163          96 : pcmk__quote_cmdline(gchar **argv)
     164             : {
     165          96 :     GString *gs = NULL;
     166             : 
     167          96 :     if (argv == NULL || argv[0] == NULL) {
     168          53 :         return NULL;
     169             :     }
     170             : 
     171          43 :     gs = g_string_sized_new(100);
     172             : 
     173         150 :     for (int i = 0; argv[i] != NULL; i++) {
     174         107 :         if (i > 0) {
     175             :             g_string_append_c(gs, ' ');
     176             :         }
     177             : 
     178         107 :         if (strchr(argv[i], ' ') == NULL) {
     179             :             /* The arg does not contain a space. */
     180         105 :             g_string_append(gs, argv[i]);
     181           2 :         } else if (strchr(argv[i], '\'') == NULL) {
     182             :             /* The arg contains a space, but not a single quote. */
     183           1 :             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           1 :             gchar *repl = string_replace(argv[i], "'", "\\\'");
     204           1 :             pcmk__g_strcat(gs, "'", repl, "'", NULL);
     205           1 :             g_free(repl);
     206             :         }
     207             :     }
     208             : 
     209          43 :     return g_string_free(gs, FALSE);
     210             : }
     211             : 
     212             : gchar **
     213          96 : pcmk__cmdline_preproc(char *const *argv, const char *special) {
     214          96 :     GPtrArray *arr = NULL;
     215          96 :     bool saw_dash_dash = false;
     216          96 :     bool copy_option = false;
     217             : 
     218          96 :     if (argv == NULL) {
     219           1 :         return NULL;
     220             :     }
     221             : 
     222          95 :     if (g_get_prgname() == NULL && argv && *argv) {
     223          83 :         gchar *basename = g_path_get_basename(*argv);
     224             : 
     225          83 :         g_set_prgname(basename);
     226          83 :         g_free(basename);
     227             :     }
     228             : 
     229          95 :     arr = g_ptr_array_new();
     230             : 
     231         306 :     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         211 :         if (saw_dash_dash == false && strcmp(argv[i], "--") == 0) {
     238           1 :             saw_dash_dash = true;
     239             :         }
     240             : 
     241         211 :         if (saw_dash_dash == true) {
     242           4 :             g_ptr_array_add(arr, g_strdup(argv[i]));
     243           2 :             continue;
     244             :         }
     245             : 
     246         209 :         if (copy_option == true) {
     247          10 :             g_ptr_array_add(arr, g_strdup(argv[i]));
     248           5 :             copy_option = false;
     249           5 :             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         204 :         if (pcmk__str_eq(argv[i], "-", pcmk__str_casei)) {
     256           2 :             g_ptr_array_add(arr, g_strdup(argv[i]));
     257           1 :             continue;
     258             :         }
     259             : 
     260             :         /* "-INFINITY" is almost certainly meant as a string, not as an option
     261             :          * list
     262             :          */
     263         203 :         if (strcmp(argv[i], "-INFINITY") == 0) {
     264           2 :             g_ptr_array_add(arr, g_strdup(argv[i]));
     265           1 :             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         226 :         if (g_str_has_prefix(argv[i], "-") && !g_str_has_prefix(argv[i], "--")) {
     272             :             /* Skip over leading dash */
     273          26 :             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          26 :             if (*ch != '\0' && *ch >= '1' && *ch <= '9') {
     281           3 :                 bool is_numeric = true;
     282             : 
     283           6 :                 while (*ch != '\0') {
     284           4 :                     if (!isdigit(*ch)) {
     285           1 :                         is_numeric = false;
     286           1 :                         break;
     287             :                     }
     288             : 
     289           3 :                     ch++;
     290             :                 }
     291             : 
     292           3 :                 if (is_numeric) {
     293           2 :                     g_ptr_array_add(arr, g_strdup_printf("%s", argv[i]));
     294           2 :                     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           1 :                     ch = argv[i]+1;
     300             :                 }
     301             :             }
     302             : 
     303          50 :             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          28 :                 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           8 :                     if (*(ch+1) != '\0') {
     314           2 :                         fprintf(stderr, "Deprecated argument format '-%c%s' used.\n", *ch, ch+1);
     315           2 :                         fprintf(stderr, "Please use '-%c %s' instead.  "
     316             :                                         "Support will be removed in a future release.\n",
     317           2 :                                 *ch, ch+1);
     318             : 
     319           2 :                         g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
     320           4 :                         g_ptr_array_add(arr, g_strdup(ch+1));
     321           2 :                         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           6 :                         g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
     329           6 :                         copy_option = true;
     330           6 :                         ch++;
     331             :                     }
     332             : 
     333             :                 /* This is a regular short argument.  Just copy it over. */
     334             :                 } else {
     335          20 :                     g_ptr_array_add(arr, g_strdup_printf("-%c", *ch));
     336          20 :                     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         352 :             g_ptr_array_add(arr, g_strdup(argv[i]));
     346             :         }
     347             :     }
     348             : 
     349          95 :     g_ptr_array_add(arr, NULL);
     350             : 
     351          95 :     return (char **) g_ptr_array_free(arr, FALSE);
     352             : }
     353             : 
     354             : G_GNUC_PRINTF(3, 4)
     355             : gboolean
     356           0 : pcmk__force_args(GOptionContext *context, GError **error, const char *format, ...) {
     357           0 :     int len = 0;
     358           0 :     char *buf = NULL;
     359           0 :     gchar **extra_args = NULL;
     360             :     va_list ap;
     361           0 :     gboolean retval = TRUE;
     362             : 
     363           0 :     va_start(ap, format);
     364           0 :     len = vasprintf(&buf, format, ap);
     365           0 :     CRM_ASSERT(len > 0);
     366           0 :     va_end(ap);
     367             : 
     368           0 :     if (!g_shell_parse_argv(buf, NULL, &extra_args, error)) {
     369           0 :         g_strfreev(extra_args);
     370           0 :         free(buf);
     371           0 :         return FALSE;
     372             :     }
     373             : 
     374           0 :     retval = g_option_context_parse_strv(context, &extra_args, error);
     375             : 
     376           0 :     g_strfreev(extra_args);
     377           0 :     free(buf);
     378           0 :     return retval;
     379             : }

Generated by: LCOV version 1.14