/* * * Copyright (c) 2020 Project CHIP Authors * Copyright (c) 2017 Nest Labs, Inc. * All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * @file * Support functions for parsing command-line arguments. * */ #include "CHIPArgParser.hpp" #if CHIP_CONFIG_ENABLE_ARG_PARSER #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * TODO: Revisit these if and when fabric ID and node ID support has * been integrated into the stack. */ #ifndef CHIP_ARG_PARSER_PARSE_FABRIC_ID #define CHIP_ARG_PARSER_PARSE_FABRIC_ID 0 #endif // CHIP_ARG_PARSER_PARSE_FABRIC_ID namespace chip { namespace ArgParser { using namespace chip; static char * MakeShortOptions(OptionSet ** optSets); static struct option * MakeLongOptions(OptionSet ** optSets); static int32_t SplitArgs(char * argStr, char **& argList, char * initialArg = nullptr); static bool GetNextArg(char *& parsePoint); static size_t CountOptionSets(OptionSet * optSets[]); static size_t CountAllOptions(OptionSet * optSets[]); static void FindOptionByIndex(OptionSet ** optSets, int optIndex, OptionSet *& optSet, OptionDef *& optDef); static void FindOptionById(OptionSet ** optSets, int optId, OptionSet *& optSet, OptionDef *& optDef); static const char ** MakeUniqueHelpGroupNamesList(OptionSet * optSets[]); static void PutStringWithNewLine(FILE * s, const char * str); static void PutStringWithBlankLine(FILE * s, const char * str); #if CHIP_CONFIG_ENABLE_ARG_PARSER_VALIDITY_CHECKS static bool SanityCheckOptions(OptionSet * optSets[]); #endif // CHIP_CONFIG_ENABLE_ARG_PARSER_VALIDITY_CHECKS static inline bool IsShortOptionChar(int ch) { return isgraph(ch); } /** * @brief * The list of OptionSets passed to the currently active ParseArgs() call. * * @details * This value will be NULL when no call to ParseArgs() is in progress. */ OptionSet ** gActiveOptionSets = nullptr; /** * @brief * Pointer to function used to print errors that occur during argument parsing. * * @details * Applications should call PrintArgError() to report errors in their option and * non-option argument handling functions, rather than printing directly to * stdout/stderr. * * Defaults to a pointer to the `DefaultPrintArgError()` function. */ void (*PrintArgError)(const char * msg, ...) = DefaultPrintArgError; /** * @fn bool ParseArgs(const char *progName, int argc, char * const argv[], OptionSet *optSets[], * NonOptionArgHandlerFunct nonOptArgHandler, bool ignoreUnknown) * * @brief * Parse a set of command line-style arguments, calling handling functions to process each * option and non-option argument. * * @param[in] progName The name of the program or context in which the arguments are * being parsed. This string will be used to prefix error * messages and warnings. * @param[in] argc The number of arguments to be parsed, plus 1. * @param[in] argv An array of argument strings to be parsed. The array length must * be 1 greater than the value specified for argc, and * argv[argc] must be set to NULL. Argument parsing begins with the * *second* array element (argv[1]); element 0 is ignored. * @param[in] optSets A list of pointers to `OptionSet` structures that define the legal * options. The supplied list must be terminated with a NULL. * @param[in] nonOptArgHandler A pointer to a function that will be called once option parsing * is complete with any remaining non-option arguments . The function * is called regardless of whether any arguments remain. If a NULL * is passed `ParseArgs()` will report an error if any non-option * arguments are present. * @param[in] ignoreUnknown If true, silently ignore any unrecognized options. * * @return `true` if all options and non-option arguments were parsed * successfully; `false` if an option was unrecognized or if one of * the handler functions failed (i.e. returned false). * * * @details * ParseArgs() takes a list of arguments (`argv`) and parses them according to a set of supplied * option definitions. The function supports both long (--opt) and short (-o) options and implements * the same option syntax as the GNU getopt_long(3) function. * * Option definitions are passed to ParseArgs() as an array of OptionSet structures (`optSets`). * Each OptionSet contains an array of option definitions and a handler function. ParseArgs() * processes option arguments in the given order, calling the respective handler function for * each recognized option. Once all options have been parsed, a separate non-option handler * function (`nonOptArgHandler`) is called once to process any remaining arguments. * * * ## OPTION SETS * * An OptionSet contains a set of option definitions along with a pointer to a handler function * that will be called when one of the associated options is encountered. Option sets also * contain help text describing the syntax and purpose of each option (see OPTION HELP below). * Option sets are designed to allow the creation of re-usable collections of related options. * This simplifies the effort needed to maintain multiple applications that accept similar options * (e.g. test applications). * * There are two patterns for defining OptionSets--one can either initialize an instance of the * OptionSet struct itself, e.g. as a static global, or subclass OptionSetBase and provide a * constructor. The latter uses a pure virtual `HandleOption()` function to delegate option * handling to the subclass. * * Lists of OptionSets are passed to the ParseArgs() function as a NULL-terminated array of pointers. * E.g.: * * static OptionSet gToolOptions = * { * HandleOption, // handler function * gToolOptionDefs, // array of option definitions * "GENERAL OPTIONS", // help group * gToolOptionHelp // option help text * }; * * static OptionSet *gOptionSets[] = * { * &gToolOptions, * &gNetworkOptions, * &gTestingOptions, * &gHelpOptions, * NULL * }; * * int main(int argc, char *argv[]) * { * if (!ParseArgs("test-app", argc, argv, gOptionSets)) * { * ... * } * } * * * ## OPTION DEFINITIONS * * Options are defined using the `OptionDef` structure. Option definitions are organized as an array * of OptionDef elements, where each element contains: the name of the option, a integer id that is * used to identify the option, and whether the option expects/allows an argument. The end of the * option array is signaled by a NULL Name field. E.g.: * * enum * { * kOpt_Listen = 1000, * kOpt_Length, * kOpt_Count, * }; * * static OptionDef gToolOptionDefs[] = * { * // NAME REQUIRES/ALLOWS ARG? ID/SHORT OPTION CHAR * // ============================================================ * { "listen", kNoArgument, kOpt_Listen }, * { "length", kArgumentRequired, kOpt_Length }, * { "count", kArgumentRequired, kOpt_Count }, * { "num", kArgumentRequired, kOpt_Count }, // alias for --count * { "debug", kArgumentOptional, 'd' }, * { "help", kNoArgument, 'h' }, * { NULL } * }; * * * ## OPTION IDS * * Option ids identify options to the code that handles them (the OptionHandler function). Option ids * are relative to the OptionSet in which they appear, and thus may be reused across different * OptionSets (however see SHORT OPTIONS below). Common convention is to start numbering option ids * at 1000, however any number > 128 can be used. Alias options can be created by using the same * option id with different option names. * * * ## SHORT OPTIONS * * Unlike getopt_long(3), ParseArgs() does not take a separate string specifying the list of short * option characters. Rather, any option whose id value falls in the range of graphical ASCII * characters will allow that character to be used as a short option. * * ParseArgs() requires that short option characters be unique across *all* OptionSets. Because of * this, the use of short options is discouraged for any OptionSets that are shared across programs * due to the significant chance for collisions. Short options characters may be reused within a * single OptionSet to allow for the creation of alias long option names. * * * ## OPTION HELP * * Each OptionSet contains an `OptionHelp` string that describes the purpose and syntax of the * associated options. These strings are used by the `PrintOptionHelp()` function to generate * option usage information. * * By convention, option help strings consist of a syntax example following by a textual * description of the option. If the option has a short version, or an alias name, it is given * before primary long name. For consistency, syntax lines are indented with 2 spaces, while * description lines are indented with 7 spaces. A single blank line follows each option * description, including the last one. * * E.g.: * * static const char *const gToolOptionHelp = * " --listen\n" * " Listen and respond to requests sent from another node.\n" * "\n" * " --length \n" * " Send requests with the specified number of bytes in the payload.\n" * "\n" * " --num, --count \n" * " Send the specified number of requests and exit.\n" * "\n" * " -d, --debug []\n" * " Set debug logging to the given level. (Default: 1)\n" * "\n" * " -h, --help\n" * " Print help information.\n" * "\n"; * * * ## OPTION HELP GROUPS * * OptionSets contain a `HelpGroupName` string which is used to group options together in the * help output. The `PrintOptionHelp()` function uses the HelpGroupName as a section title in * the generated usage output. If multiple OptionSets have the same HelpGroupName, * PrintOptionHelp() will print the option help for the different OptionSets together under * a common section title. * */ bool ParseArgs(const char * progName, int argc, char * const argv[], OptionSet * optSets[], NonOptionArgHandlerFunct nonOptArgHandler, bool ignoreUnknown) { bool res = false; char optName[64]; char * optArg; char * shortOpts = nullptr; struct option * longOpts = nullptr; OptionSet * curOptSet; OptionDef * curOpt; bool handlerRes; #if CHIP_CONFIG_NON_POSIX_LONG_OPT int lastOptIndex = 0; int subOptIndex = 0; int currentOptIndex = 0; #endif // CHIP_CONFIG_NON_POSIX_LONG_OPT // The getopt() functions do not support recursion, so exit immediately with an // error if called recursively. if (gActiveOptionSets != nullptr) { PrintArgError("INTERNAL ERROR: ParseArgs() called recursively\n", progName); return false; } // The C standard mandates that argv[argc] == NULL and certain versions of getopt() require this // to function properly. So fail if this is not true. if (argv[argc] != nullptr) { PrintArgError("INTERNAL ERROR: argv[argc] != NULL\n", progName); return false; } // Set gActiveOptionSets to the current option set list. gActiveOptionSets = optSets; #if CHIP_CONFIG_ENABLE_ARG_PARSER_VALIDITY_CHECKS if (!SanityCheckOptions(optSets)) goto done; #endif // Generate a short options string in the format expected by getopt_long(). shortOpts = MakeShortOptions(optSets); if (shortOpts == nullptr) { PrintArgError("%s: Memory allocation failure\n", progName); goto done; } // Generate a list of long option structures in the format expected by getopt_long(). longOpts = MakeLongOptions(optSets); if (longOpts == nullptr) { PrintArgError("%s: Memory allocation failure\n", progName); goto done; } // Force getopt() to reset its internal state. optind = 0; // Process any option arguments... while (true) { int id; int optIndex = -1; // Attempt to match the current option argument (argv[optind]) against the defined long and short options. optarg = nullptr; optopt = 0; #if CHIP_CONFIG_NON_POSIX_LONG_OPT // to check if index has changed lastOptIndex = currentOptIndex; // optind will not increment on error, this is why we need to keep track of the current option // this is for use when getopt_long fails to find the option and we need to print the error currentOptIndex = optind; // if it's the first run, optind is not set and we need to find the first option ourselves if (!currentOptIndex) { while (currentOptIndex < argc) { currentOptIndex++; if (*argv[currentOptIndex] == '-') { break; } } } // similarly we need to keep track of short opts index for groups like "-fba" // if the index has not changed that means we are still analysing the same group if (lastOptIndex != currentOptIndex) { subOptIndex = 0; } else { subOptIndex++; } #endif // CHIP_CONFIG_NON_POSIX_LONG_OPT id = getopt_long(argc, argv, shortOpts, longOpts, &optIndex); // Stop if there are no more options. if (id == -1) break; // If the current option is unrecognized, fail with an error message unless ignoreUnknown == true. if (id == '?') { if (ignoreUnknown) continue; #if CHIP_CONFIG_NON_POSIX_LONG_OPT // getopt_long doesn't tell us if the option which failed to match is long or short so check bool isLongOption = false; if (strlen(argv[currentOptIndex]) > 2 && argv[currentOptIndex][1] == '-') { isLongOption = true; } if (optopt == 0 || isLongOption) { // getopt_long function incorrectly treats unknown long option as short opt group if (subOptIndex == 0) { PrintArgError("%s: Unknown option: %s\n", progName, argv[currentOptIndex]); } } else if (optopt == '?') { PrintArgError("%s: Unknown option: -%c\n", progName, argv[currentOptIndex][subOptIndex + 1]); } else { PrintArgError("%s: Unknown option: -%c\n", progName, optopt); } #else if (optopt != 0) PrintArgError("%s: Unknown option: -%c\n", progName, optopt); else PrintArgError("%s: Unknown option: %s\n", progName, argv[optind - 1]); #endif // CHIP_CONFIG_NON_POSIX_LONG_OPT goto done; } // If the option was recognized, but it is lacking an argument, fail with // an error message. if (id == ':') { // NOTE: with the way getopt_long() works, it is impossible to tell whether the option that // was missing an argument was a long option or a short option. #if CHIP_CONFIG_NON_POSIX_LONG_OPT PrintArgError("%s: Missing argument for %s option\n", progName, argv[currentOptIndex]); #else PrintArgError("%s: Missing argument for %s option\n", progName, argv[optind - 1]); #endif // CHIP_CONFIG_NON_POSIX_LONG_OPT goto done; } // If a long option was matched... if (optIndex != -1) { // Locate the option set and definition using the index value returned by getopt_long(). FindOptionByIndex(optSets, optIndex, curOptSet, curOpt); // Form a string containing the name of the option as it appears on the command line. snprintf(optName, sizeof(optName), "--%s", curOpt->Name); } // Otherwise a short option was matched... else { // Locate the option set and definition using the option id. FindOptionById(optSets, id, curOptSet, curOpt); // Form a string containing the name of the short option as it would appears on the // command line if given by itself. snprintf(optName, sizeof(optName), "-%c", id); } // Prevent handlers from inadvertently using the getopt global optarg. optArg = optarg; optarg = nullptr; // Call the option handler function defined for the matching option set. // Exit immediately if the option handler failed. handlerRes = curOptSet->OptionHandler(progName, curOptSet, id, optName, optArg); if (!handlerRes) goto done; } // If supplied, call the non-option argument handler with the remaining arguments (if any). if (nonOptArgHandler != nullptr) { if (!nonOptArgHandler(progName, argc - optind, argv + optind)) goto done; } // otherwise, if there are additional arguments, fail with an error. else if (optind < argc) { PrintArgError("%s: Unexpected argument: %s\n", progName, argv[optind]); goto done; } res = true; done: if (shortOpts != nullptr) chip::Platform::MemoryFree(shortOpts); if (longOpts != nullptr) chip::Platform::MemoryFree(longOpts); gActiveOptionSets = nullptr; return res; } bool ParseArgs(const char * progName, int argc, char * const argv[], OptionSet * optSets[], NonOptionArgHandlerFunct nonOptArgHandler) { return ParseArgs(progName, argc, argv, optSets, nonOptArgHandler, false); } bool ParseArgs(const char * progName, int argc, char * const argv[], OptionSet * optSets[]) { return ParseArgs(progName, argc, argv, optSets, nullptr, false); } /** * @brief * Parse a set of arguments from a given string. * * @param[in] progName The name of the program or context in which the arguments are * being parsed. This string will be used to prefix error * messages and warnings. * @param[in] argStr A string containing options and arguments to be parsed. * @param[in] optSets A list of pointers to `OptionSet` structures that define the legal * options. The supplied list must be terminated with a NULL. * @param[in] nonOptArgHandler A pointer to a function that will be called once option parsing * is complete with any remaining non-option arguments . The function * is called regardless of whether any arguments remain. If a NULL * is passed `ParseArgs()` will report an error if any non-option * arguments are present. * @param[in] ignoreUnknown If true, silently ignore any unrecognized options. * * @return `true` if all options and non-option arguments were parsed * successfully; `false` if an option was unrecognized, if one of * the handler functions failed (i.e. returned false) or if an * internal error occurred. * * @details * ParseArgsFromString() splits a given string (`argStr`) into a set of arguments and parses the * arguments using the ParseArgs() function. * * The syntax of the input strings is similar to unix shell command syntax, but with a simplified * quoting scheme. Specifically: * * - Arguments are delimited by whitespace, unless the whitespace is quoted or escaped. * * - A backslash escapes the following character, causing it to be treated as a normal character. * The backslash itself is stripped. * * - Single or double quotes begin/end quoted substrings. Within a substring, the only special * characters are backslash, which escapes the next character, and the corresponding end quote. * The begin/end quote characters are stripped. * * E.g.: * * --listen --count 10 --sw-version '1.0 (DEVELOPMENT)' "--hostname=nest.com" * */ bool ParseArgsFromString(const char * progName, const char * argStr, OptionSet * optSets[], NonOptionArgHandlerFunct nonOptArgHandler, bool ignoreUnknown) { char ** argv = nullptr; int argc; bool res; chip::Platform::ScopedMemoryString argStrCopy(argStr, strlen(argStr)); if (!argStrCopy) { PrintArgError("%s: Memory allocation failure\n", progName); return false; } argc = SplitArgs(argStrCopy.Get(), argv, const_cast(progName)); if (argc < 0) { PrintArgError("%s: Memory allocation failure\n", progName); return false; } res = ParseArgs(progName, argc, argv, optSets, nonOptArgHandler, ignoreUnknown); chip::Platform::MemoryFree(argv); return res; } bool ParseArgsFromString(const char * progName, const char * argStr, OptionSet * optSets[], NonOptionArgHandlerFunct nonOptArgHandler) { return ParseArgsFromString(progName, argStr, optSets, nonOptArgHandler, false); } bool ParseArgsFromString(const char * progName, const char * argStr, OptionSet * optSets[]) { return ParseArgsFromString(progName, argStr, optSets, nullptr, false); } /** * @brief * Parse a set of arguments from a named environment variable * * @param[in] progName The name of the program or context in which the arguments are * being parsed. This string will be used to prefix error * messages and warnings. * @param[in] varName The name of the environment variable. * @param[in] optSets A list of pointers to `OptionSet` structures that define the legal * options. The supplied list must be terminated with a NULL. * @param[in] nonOptArgHandler A pointer to a function that will be called once option parsing * is complete with any remaining non-option arguments . The function * is called regardless of whether any arguments remain. If a NULL * is passed `ParseArgs()` will report an error if any non-option * arguments are present. * @param[in] ignoreUnknown If true, silently ignore any unrecognized options. * * @return `true` if all options and non-option arguments were parsed * successfully, or if the specified environment variable is not set; * `false` if an option was unrecognized, if one of the handler * functions failed (i.e. returned false) or if an internal error * occurred. * * @details * ParseArgsFromEnvVar() reads a named environment variable and passes the value to `ParseArgsFromString()` * for parsing. If the environment variable is not set, the function does nothing. */ bool ParseArgsFromEnvVar(const char * progName, const char * varName, OptionSet * optSets[], NonOptionArgHandlerFunct nonOptArgHandler, bool ignoreUnknown) { const char * argStr = getenv(varName); if (argStr == nullptr) return true; return ParseArgsFromString(progName, argStr, optSets, nonOptArgHandler, ignoreUnknown); } bool ParseArgsFromEnvVar(const char * progName, const char * varName, OptionSet * optSets[]) { return ParseArgsFromEnvVar(progName, varName, optSets, nullptr, false); } bool ParseArgsFromEnvVar(const char * progName, const char * varName, OptionSet * optSets[], NonOptionArgHandlerFunct nonOptArgHandler) { return ParseArgsFromEnvVar(progName, varName, optSets, nonOptArgHandler, false); } /** * @brief * Print the help text for a specified list of options to a stream. * * @param[in] optSets A list of pointers to `OptionSet` structures that contain the * help text to print. * @param[in] s The FILE stream to which the help text should be printed. * */ void PrintOptionHelp(OptionSet * optSets[], FILE * s) { // Get a list of the unique help group names for the given option sets. const char ** helpGroupNames = MakeUniqueHelpGroupNamesList(optSets); if (helpGroupNames == nullptr) { PrintArgError("Memory allocation failure\n"); return; } // For each help group... for (size_t nameIndex = 0; helpGroupNames[nameIndex] != nullptr; nameIndex++) { // Print the group name. PutStringWithBlankLine(s, helpGroupNames[nameIndex]); // Print the option help text for all options that have the same group name. for (size_t optSetIndex = 0; optSets[optSetIndex] != nullptr; optSetIndex++) if (strcasecmp(helpGroupNames[nameIndex], optSets[optSetIndex]->HelpGroupName) == 0) { PutStringWithBlankLine(s, optSets[optSetIndex]->OptionHelp); } } chip::Platform::MemoryFree(helpGroupNames); } /** * @brief * Print an error message associated with argument parsing. * * @param[in] msg The message to be printed. * * @details * Default function used to print error messages that arise due to the parsing * of arguments. * * Applications should call through the PrintArgError function pointer, rather * than calling this function directly. */ void ENFORCE_FORMAT(1, 2) DefaultPrintArgError(const char * msg, ...) { va_list ap; va_start(ap, msg); vfprintf(stderr, msg, ap); va_end(ap); } /** * Parse a string as a boolean value. * * This function accepts the following input values (case-insensitive): * "true", "yes", "t", "y", "1", "false", "no", "f", "n", "0". * * @param[in] str A pointer to a NULL-terminated C string representing * the value to parse. * @param[out] output A reference to storage for a bool to which the parsed * value will be stored on success. * * @return true on success; otherwise, false on failure. */ bool ParseBoolean(const char * str, bool & output) { if (strcasecmp(str, "true") == 0 || strcasecmp(str, "yes") == 0 || ((str[0] == '1' || str[0] == 't' || str[0] == 'T' || str[0] == 'y' || str[0] == 'Y') && str[1] == 0)) { output = true; return true; } if (strcasecmp(str, "false") == 0 || strcasecmp(str, "no") == 0 || ((str[0] == '0' || str[0] == 'f' || str[0] == 'F' || str[0] == 'n' || str[0] == 'N') && str[1] == 0)) { output = false; return true; } return false; } /** * Parse and attempt to convert a string to a 64-bit unsigned integer, * applying the appropriate interpretation based on the base parameter. * * @param[in] str A pointer to a NULL-terminated C string representing * the integer to parse. * @param[out] output A reference to storage for a 64-bit unsigned integer * to which the parsed value will be stored on success. * @param[in] base The base according to which the string should be * interpreted and parsed. If 0 or 16, the string may * be hexadecimal and prefixed with "0x". Otherwise, a 0 * is implied as 10 unless a leading 0 is encountered in * which 8 is implied. * * @return true on success; otherwise, false on failure. */ bool ParseInt(const char * str, uint64_t & output, int base) { char * parseEnd; errno = 0; output = strtoull(str, &parseEnd, base); return parseEnd > str && *parseEnd == 0 && (output != ULLONG_MAX || errno == 0); } /** * Parse and attempt to convert a string to a 32-bit unsigned integer, * applying the appropriate interpretation based on the base parameter. * * @param[in] str A pointer to a NULL-terminated C string representing * the integer to parse. * @param[out] output A reference to storage for a 32-bit unsigned integer * to which the parsed value will be stored on success. * @param[in] base The base according to which the string should be * interpreted and parsed. If 0 or 16, the string may * be hexadecimal and prefixed with "0x". Otherwise, a 0 * is implied as 10 unless a leading 0 is encountered in * which 8 is implied. * * @return true on success; otherwise, false on failure. */ bool ParseInt(const char * str, uint32_t & output, int base) { char * parseEnd; unsigned long v; errno = 0; v = strtoul(str, &parseEnd, base); if (!CanCastTo(v)) { return false; } output = static_cast(v); return parseEnd > str && *parseEnd == 0 && (v != ULONG_MAX || errno == 0); } /** * Parse and attempt to convert a string to a 32-bit signed integer, * applying the appropriate interpretation based on the base parameter. * * @param[in] str A pointer to a NULL-terminated C string representing * the integer to parse. * @param[out] output A reference to storage for a 32-bit signed integer * to which the parsed value will be stored on success. * @param[in] base The base according to which the string should be * interpreted and parsed. If 0 or 16, the string may * be hexadecimal and prefixed with "0x". Otherwise, a 0 * is implied as 10 unless a leading 0 is encountered in * which 8 is implied. * * @return true on success; otherwise, false on failure. */ bool ParseInt(const char * str, int32_t & output, int base) { char * parseEnd; long v; errno = 0; v = strtol(str, &parseEnd, base); if (!CanCastTo(v)) { return false; } output = static_cast(v); return parseEnd > str && *parseEnd == 0 && ((v != LONG_MIN && v != LONG_MAX) || errno == 0); } /** * Parse and attempt to convert a string to a 16-bit unsigned integer, * applying the appropriate interpretation based on the base parameter. * * @param[in] str A pointer to a NULL-terminated C string representing * the integer to parse. * @param[out] output A reference to storage for a 16-bit unsigned integer * to which the parsed value will be stored on success. * @param[in] base The base according to which the string should be * interpreted and parsed. If 0 or 16, the string may * be hexadecimal and prefixed with "0x". Otherwise, a 0 * is implied as 10 unless a leading 0 is encountered in * which 8 is implied. * * @return true on success; otherwise, false on failure. */ bool ParseInt(const char * str, uint16_t & output, int base) { uint32_t v; if (!ParseInt(str, v, base) || !CanCastTo(v)) { return false; } output = static_cast(v); return true; } /** * Parse and attempt to convert a string to a 8-bit unsigned integer, * applying the appropriate interpretation based on the base parameter. * * @param[in] str A pointer to a NULL-terminated C string representing * the integer to parse. * @param[out] output A reference to storage for a 8-bit unsigned integer * to which the parsed value will be stored on success. * @param[in] base The base according to which the string should be * interpreted and parsed. If 0 or 16, the string may * be hexadecimal and prefixed with "0x". Otherwise, a 0 * is implied as 10 unless a leading 0 is encountered in * which 8 is implied. * * @return true on success; otherwise, false on failure. */ bool ParseInt(const char * str, uint8_t & output, int base) { uint32_t v; if (!ParseInt(str, v, base) || !CanCastTo(v)) { return false; } output = static_cast(v); return true; } /** * Parse and attempt to convert a string interpreted as a decimal * value to a 64-bit unsigned integer, applying the appropriate * interpretation based on the base parameter. * * @param[in] str A pointer to a NULL-terminated C string representing * the integer to parse. * @param[out] output A reference to storage for a 64-bit unsigned integer * to which the parsed value will be stored on success. * * @return true on success; otherwise, false on failure. */ bool ParseInt(const char * str, uint64_t & output) { const int base = 10; return ParseInt(str, output, base); } /** * Parse and attempt to convert a string interpreted as a decimal * value to a 32-bit unsigned integer, applying the appropriate * interpretation based on the base parameter. * * @param[in] str A pointer to a NULL-terminated C string representing * the integer to parse. * @param[out] output A reference to storage for a 32-bit unsigned integer * to which the parsed value will be stored on success. * * @return true on success; otherwise, false on failure. */ bool ParseInt(const char * str, uint32_t & output) { const int base = 10; return ParseInt(str, output, base); } /** * Parse and attempt to convert a string interpreted as a decimal * value to a 32-bit signed integer, applying the appropriate * interpretation based on the base parameter. * * @param[in] str A pointer to a NULL-terminated C string representing * the integer to parse. * @param[out] output A reference to storage for a 32-bit signed integer * to which the parsed value will be stored on success. * * @return true on success; otherwise, false on failure. */ bool ParseInt(const char * str, int32_t & output) { const int base = 10; return ParseInt(str, output, base); } /** * Parse and attempt to convert a string interpreted as a decimal * value to a 16-bit unsigned integer, applying the appropriate * interpretation based on the base parameter. * * @param[in] str A pointer to a NULL-terminated C string representing * the integer to parse. * @param[out] output A reference to storage for a 16-bit unsigned integer * to which the parsed value will be stored on success. * * @return true on success; otherwise, false on failure. */ bool ParseInt(const char * str, uint16_t & output) { const int base = 10; uint32_t output32 = 0; if ((ParseInt(str, output32, base)) && (output32 <= USHRT_MAX)) { output = ((1 << 16) - 1) & output32; return true; } return false; } /** * Parse and attempt to convert a string interpreted as a decimal * value to a 16-bit signed integer, applying the appropriate * interpretation based on the base parameter. * * @param[in] str A pointer to a NULL-terminated C string representing * the integer to parse. * @param[out] output A reference to storage for a 16-bit signed integer * to which the parsed value will be stored on success. * * @return true on success; otherwise, false on failure. */ bool ParseInt(const char * str, int16_t & output) { const int base = 10; int32_t output32 = 0; if ((ParseInt(str, output32, base)) && (output32 <= SHRT_MAX && output32 >= SHRT_MIN)) { output = static_cast(output32); return true; } return false; } /** * Parse and attempt to convert a string interpreted as a decimal * value to a 8-bit unsigned integer, applying the appropriate * interpretation based on the base parameter. * * @param[in] str A pointer to a NULL-terminated C string representing * the integer to parse. * @param[out] output A reference to storage for a 8-bit unsigned integer * to which the parsed value will be stored on success. * * @return true on success; otherwise, false on failure. */ bool ParseInt(const char * str, uint8_t & output) { const int base = 10; uint32_t output32 = 0; if ((ParseInt(str, output32, base)) && (output32 <= UCHAR_MAX)) { output = ((1 << 8) - 1) & output32; return true; } return false; } #if CHIP_ARG_PARSER_PARSE_FABRIC_ID /** * Parse a CHIP fabric id in text form. * * @param[in] str A pointer to a NULL-terminated C string containing * the fabric id to parse. * @param[out] output A reference to an uint64_t lvalue in which the * parsed value will be stored on success. * @param[in] allowReserved If true, allow the parsing of fabric ids in the * reserved range. * * @return true if the value was successfully parsed; false if not. * * @details * The ParseFabricId() function accepts a 64-bit fabric id given in hex format, * with or without a leading '0x'. */ bool ParseFabricId(const char * str, uint64_t & fabricId, bool allowReserved) { char * parseEnd; errno = 0; fabricId = strtoull(str, &parseEnd, 16); return parseEnd > str && *parseEnd == 0 && (fabricId != ULLONG_MAX || errno == 0) && (allowReserved || fabricId < kReservedFabricIdStart); } #endif // CHIP_ARG_PARSER_PARSE_FABRIC_ID /** * Parse and attempt to convert a string to a 16-bit unsigned subnet * ID, interpretting the string as hexadecimal. * * @param[in] str A pointer to a NULL-terminated C string * representing the subnet ID, formatted as a * hexadecimal, to parse. * @param[in,out] subnetId A reference to storage for a 16-bit unsigned * integer to which the parsed subnet ID value * will be stored on success. * * @return true on success; otherwise, false on failure. */ bool ParseSubnetId(const char * str, uint16_t & subnetId) { char * parseEnd; unsigned long temp; bool valid; // Reset errno per the strtoul manual page. errno = 0; // Attempt to parse the subnet ID as a hexadecimal number. temp = strtoul(str, &parseEnd, 16); // Determine if the parse and conversion were valid. valid = (parseEnd > str && // Parsed some valid hexadecimal digits *parseEnd == 0 && // Encountered no invalid hexadecimal digits (temp != ULONG_MAX || errno == 0) && // No overflow (ERANGE) or invalid base (EINVAL) errors temp <= USHRT_MAX); // Parsed value is valid for the domain (subnet ID) if (valid) { subnetId = static_cast(temp); } return valid; } /** * Parse a string of bytes given in hex form. * * @param[in] hexStr A pointer to the string to parse. * @param[in] strLen The number of characters in hexStr to parse. * @param[in] outBuf A pointer to a buffer into which the parse bytes will * be stored. * @param[in] outBufSize The size of the buffer pointed at by `outBuf`. * @param[out] outDataLen A reference to an integer that will receive the total * number of bytes parsed. In the event outBuf is not * big enough to hold the given number of bytes, `outDataLen` * will be set to UINT32_MAX. * * @return true if the value was successfully parsed; false if the input data is malformed, * or if `outBuf` is too small. * * @details * ParseHexString() expects the input to be in the form of pairs of hex digits (upper or lower case). * Hex pairs can optionally be separated by any of the following characters: colon, semicolon, comma, period or dash. * Additionally, whitespace characters anywhere in the input string are ignored. */ bool ParseHexString(const char * hexStr, uint32_t strLen, uint8_t * outBuf, uint32_t outBufSize, uint32_t & outDataLen) { bool isFirstNibble = true; uint8_t firstNibbleVal = 0; const char * p = hexStr; uint32_t dataLen = 0; outDataLen = 0; for (; strLen > 0; p++, strLen--) { char c = *p; uint8_t nibbleVal; if (c == 0) break; if (c >= '0' && c <= '9') nibbleVal = static_cast(c - '0'); else if (c >= 'a' && c <= 'f') nibbleVal = static_cast(10 + (c - 'a')); else if (c >= 'A' && c <= 'F') nibbleVal = static_cast(10 + (c - 'A')); else if (isspace(c)) continue; else if (isFirstNibble && (c == ':' || c == ';' || c == ',' || c == '.' || c == '-')) continue; else { outDataLen = static_cast(p - hexStr); return false; } if (isFirstNibble) { firstNibbleVal = nibbleVal; isFirstNibble = false; } else { if (outBufSize == 0) { outDataLen = UINT32_MAX; return false; } *outBuf = static_cast(firstNibbleVal << 4 | nibbleVal); outBuf++; outBufSize--; dataLen++; isFirstNibble = true; } } if (!isFirstNibble) { outDataLen = static_cast(p - hexStr); return false; } outDataLen = dataLen; return true; } // ===== HelpOptions Methods ===== HelpOptions::HelpOptions(const char * appName, const char * appUsage, const char * appVersion) : HelpOptions(appName, appUsage, appVersion, nullptr) {} HelpOptions::HelpOptions(const char * appName, const char * appUsage, const char * appVersion, const char * appDesc) { // clang-format off static OptionDef optionDefs[] = { { "help", kNoArgument, 'h' }, { "version", kNoArgument, 'v' }, { } }; // clang-format on OptionDefs = optionDefs; HelpGroupName = "HELP OPTIONS"; OptionHelp = " -h, --help\n" " Print this output and then exit.\n" "\n" " -v, --version\n" " Print the version and then exit.\n" "\n"; AppName = appName; AppUsage = appUsage; AppVersion = appVersion; AppDesc = appDesc; } /** * Print a short description of the command's usage followed by instructions on how to get more help. */ void HelpOptions::PrintBriefUsage(FILE * s) const { PutStringWithNewLine(s, AppUsage); fprintf(s, "Try `%s --help' for more information.\n", AppName); } /** * Print the full usage information, including information on all available options. */ void HelpOptions::PrintLongUsage(OptionSet ** optSets, FILE * s) const { PutStringWithBlankLine(s, AppUsage); if (AppDesc != nullptr) { PutStringWithBlankLine(s, AppDesc); } PrintOptionHelp(optSets, s); } void HelpOptions::PrintVersion(FILE * s) const { fprintf(s, "%s ", AppName); PutStringWithNewLine(s, (AppVersion != nullptr) ? AppVersion : "(unknown version)"); } bool HelpOptions::HandleOption(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) { switch (id) { case 'h': PrintLongUsage(gActiveOptionSets, stdout); exit(EXIT_SUCCESS); break; case 'v': PrintVersion(stdout); exit(EXIT_SUCCESS); break; default: PrintArgError("%s: INTERNAL ERROR: Unhandled option: %s\n", progName, name); return false; } return true; } // ===== Private/Internal Methods ===== OptionSetBase::OptionSetBase() { OptionHandler = CallHandleFunct; } bool OptionSetBase::CallHandleFunct(const char * progName, OptionSet * optSet, int id, const char * name, const char * arg) { return static_cast(optSet)->HandleOption(progName, optSet, id, name, arg); } static char * MakeShortOptions(OptionSet ** optSets) { size_t i = 0; // Count the number of options. size_t totalOptions = CountAllOptions(optSets); // Allocate a block of memory big enough to hold the maximum possible size short option string. // The buffer needs to be big enough to hold up to 3 characters per short option plus an initial // ":" and a terminating null. size_t arraySize = 2 + (totalOptions * 3); char * shortOpts = static_cast(chip::Platform::MemoryAlloc(arraySize)); if (shortOpts == nullptr) return nullptr; // Prefix the string with ':'. This tells getopt() to signal missing option arguments distinct // from unknown options. shortOpts[i++] = ':'; // For each option set... for (; *optSets != nullptr; optSets++) { // For each option in the current option set... for (OptionDef * optDef = (*optSets)->OptionDefs; optDef->Name != nullptr; optDef++) { // If the option id (val) is suitable as a short option character, add it to the short // option string. Append ":" if the option requires an argument and "::" if the argument // is optional. if (IsShortOptionChar(optDef->Id)) { shortOpts[i++] = static_cast(optDef->Id); if (optDef->ArgType != kNoArgument) shortOpts[i++] = ':'; if (optDef->ArgType == kArgumentOptional) shortOpts[i++] = ':'; } } } // Terminate the short options string. shortOpts[i++] = 0; return shortOpts; } static struct option * MakeLongOptions(OptionSet ** optSets) { size_t totalOptions = CountAllOptions(optSets); // Allocate an array to hold the list of long options. size_t arraySize = (sizeof(struct option) * (totalOptions + 1)); struct option * longOpts = static_cast(chip::Platform::MemoryAlloc(arraySize)); if (longOpts == nullptr) return nullptr; // For each option set... size_t i = 0; for (; *optSets != nullptr; optSets++) { // Copy the option definitions into the long options array. for (OptionDef * optDef = (*optSets)->OptionDefs; optDef->Name != nullptr; optDef++) { longOpts[i].name = optDef->Name; longOpts[i].has_arg = static_cast(optDef->ArgType); longOpts[i].flag = nullptr; longOpts[i].val = optDef->Id; i++; } } // Terminate the long options array. longOpts[i].name = nullptr; return longOpts; } static int32_t SplitArgs(char * argStr, char **& argList, char * initialArg) { enum { InitialArgListSize = 10 }; size_t argListSize = 0; int32_t argCount = 0; // Allocate an array to hold pointers to the arguments. argList = static_cast(chip::Platform::MemoryAlloc(sizeof(char *) * InitialArgListSize)); if (argList == nullptr) return -1; argListSize = InitialArgListSize; // If an initial argument was supplied, make it the first argument in the array. if (initialArg != nullptr) { argList[0] = initialArg; argCount = 1; } // Parse arguments from the input string until it is exhausted. while (true) { char * nextArg = argStr; // Get the argument in the input string. Note that this modifies the string buffer. if (!GetNextArg(argStr)) break; // Grow the arg list array if needed. Note that we reserve one slot at the end of the array // for a NULL entry. if (argListSize == static_cast(argCount + 1)) { argListSize *= 2; argList = static_cast(chip::Platform::MemoryRealloc(argList, argListSize)); if (argList == nullptr) return -1; } // Append the argument. argList[argCount++] = nextArg; } // Set the last element in the array to NULL, but do not include this in the count of elements. // This is mandated by the C standard and some versions of getopt_long() depend on it. argList[argCount] = nullptr; return argCount; } static bool GetNextArg(char *& parsePoint) { char quoteChar = 0; char * argEnd = parsePoint; // Skip any leading whitespace. while (*parsePoint != 0 && isspace(*parsePoint)) parsePoint++; // Return false if there are no further arguments. if (*parsePoint == 0) return false; // Iterate over characters until we find the end of an argument. // As we iterate, we will accumulate the unquoted and unescaped // argument characters in the input buffer starting at the initial // parsePoint position. while (*parsePoint != 0) { // If the current character is a backslash that is not at the end of // the string, skip the backslash but copy the following character // verbatim into the argument string. if (*parsePoint == '\\' && *(parsePoint + 1) != 0) { parsePoint++; } // Otherwise, if not within a quoted substring... else if (quoteChar == 0) { // Whitespace marks the end of the argument. if (isspace(*parsePoint)) { parsePoint++; break; } // If the character is a quote character, enter quoted substring mode. if (*parsePoint == '"' || *parsePoint == '\'') { quoteChar = *parsePoint++; continue; } } // Otherwise, the parse point is within a quoted substring, so... else { // A corresponding quote character marks the end of the quoted string. if (*parsePoint == quoteChar) { quoteChar = 0; parsePoint++; continue; } } // Copy the current character to the end of the argument string. *argEnd++ = *parsePoint++; } // Terminate the argument string. *argEnd = 0; return true; } static size_t CountOptionSets(OptionSet ** optSets) { size_t count = 0; for (; *optSets != nullptr; optSets++) count++; return count; } static size_t CountAllOptions(OptionSet ** optSets) { size_t count = 0; for (; *optSets != nullptr; optSets++) for (OptionDef * optDef = (*optSets)->OptionDefs; optDef->Name != nullptr; optDef++) count++; return count; } static void FindOptionByIndex(OptionSet ** optSets, int optIndex, OptionSet *& optSet, OptionDef *& optDef) { for (optSet = *optSets; optSet != nullptr; optSet = *++optSets) for (optDef = (*optSets)->OptionDefs; optDef->Name != nullptr; optDef++) if (optIndex-- == 0) return; optSet = nullptr; optDef = nullptr; } static void FindOptionById(OptionSet ** optSets, int optId, OptionSet *& optSet, OptionDef *& optDef) { for (optSet = *optSets; optSet != nullptr; optSet = *++optSets) for (optDef = (*optSets)->OptionDefs; optDef->Name != nullptr; optDef++) if (optDef->Id == optId) return; optSet = nullptr; optDef = nullptr; } static const char ** MakeUniqueHelpGroupNamesList(OptionSet * optSets[]) { size_t numOptSets = CountOptionSets(optSets); size_t numGroups = 0; const char ** groupNames = static_cast(chip::Platform::MemoryAlloc(sizeof(const char *) * (numOptSets + 1))); if (groupNames == nullptr) return nullptr; for (size_t optSetIndex = 0; optSetIndex < numOptSets; optSetIndex++) { if (optSets[optSetIndex] != nullptr && optSets[optSetIndex]->OptionDefs[0].Name != nullptr) { for (size_t i = 0; i < numGroups; i++) if (strcasecmp(groupNames[i], optSets[optSetIndex]->HelpGroupName) == 0) goto skipDup; groupNames[numGroups++] = optSets[optSetIndex]->HelpGroupName; skipDup:; } } groupNames[numGroups] = nullptr; return groupNames; } static void PutStringWithNewLine(FILE * s, const char * str) { size_t strLen = strlen(str); fputs(str, s); if (strLen == 0 || str[strLen - 1] != '\n') fputs("\n", s); } static void PutStringWithBlankLine(FILE * s, const char * str) { size_t strLen = strlen(str); fputs(str, s); if (strLen < 1 || str[strLen - 1] != '\n') fputs("\n", s); if (strLen < 2 || str[strLen - 2] != '\n') fputs("\n", s); } #if CHIP_CONFIG_ENABLE_ARG_PARSER_VALIDITY_CHECKS static bool SanityCheckOptions(OptionSet * optSets[]) { bool res = true; // Verify OptionHandler pointer for (OptionSet ** optSetP = optSets; *optSetP != nullptr; optSetP++) { if ((*optSetP)->OptionHandler == nullptr) { PrintArgError("INTERNAL ERROR: Null OptionHandler in OptionSet (%s)\n", (*optSetP)->HelpGroupName); res = false; } } // Verify that no two option sets use the same short option character. // (Re-use of the same short option character is allowed within a single option set // to allow for aliasing of long options). for (OptionSet ** optSetP = optSets; *optSetP != nullptr; optSetP++) for (OptionDef * optionDef = (*optSetP)->OptionDefs; optionDef->Name != nullptr; optionDef++) if (IsShortOptionChar(optionDef->Id)) { for (OptionSet ** optSetP2 = optSets; *optSetP2 != nullptr; optSetP2++) if (optSetP2 != optSetP) { for (OptionDef * optionDef2 = (*optSetP2)->OptionDefs; optionDef2->Name != nullptr; optionDef2++) if (optionDef->Id == optionDef2->Id) { PrintArgError("INTERNAL ERROR: Multiple command line options configured to use " "the same short option character (-%c): --%s, --%s\n", optionDef->Id, optionDef->Name, optionDef2->Name); res = false; } } } return res; } #endif // CHIP_CONFIG_ENABLE_ARG_PARSER_VALIDITY_CHECKS } // namespace ArgParser } // namespace chip #endif // CHIP_CONFIG_ENABLE_ARG_PARSER