blob: b791f16a560196c6c423d04af38c3f0934263597 [file] [log] [blame]
/*
*
* 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 <climits>
#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <inttypes.h>
#include <lib/support/SafeInt.h>
#include <limits.h>
#include <stdarg.h>
#include <stdint.h>
#include <string.h>
#include <strings.h>
#include <unistd.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CHIPMemString.h>
#include <lib/support/EnforceFormat.h>
#include <lib/support/logging/Constants.h>
/*
* 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 <num>\n"
* " Send requests with the specified number of bytes in the payload.\n"
* "\n"
* " --num, --count <num>\n"
* " Send the specified number of requests and exit.\n"
* "\n"
* " -d, --debug [<level>]\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<char *>(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<uint32_t>(v))
{
return false;
}
output = static_cast<uint32_t>(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<int32_t>(v))
{
return false;
}
output = static_cast<int32_t>(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<uint16_t>(v))
{
return false;
}
output = static_cast<uint16_t>(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<uint8_t>(v))
{
return false;
}
output = static_cast<uint8_t>(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<int16_t>(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<uint16_t>(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<uint8_t>(c - '0');
else if (c >= 'a' && c <= 'f')
nibbleVal = static_cast<uint8_t>(10 + (c - 'a'));
else if (c >= 'A' && c <= 'F')
nibbleVal = static_cast<uint8_t>(10 + (c - 'A'));
else if (isspace(c))
continue;
else if (isFirstNibble && (c == ':' || c == ';' || c == ',' || c == '.' || c == '-'))
continue;
else
{
outDataLen = static_cast<decltype(strLen)>(p - hexStr);
return false;
}
if (isFirstNibble)
{
firstNibbleVal = nibbleVal;
isFirstNibble = false;
}
else
{
if (outBufSize == 0)
{
outDataLen = UINT32_MAX;
return false;
}
*outBuf = static_cast<uint8_t>(firstNibbleVal << 4 | nibbleVal);
outBuf++;
outBufSize--;
dataLen++;
isFirstNibble = true;
}
}
if (!isFirstNibble)
{
outDataLen = static_cast<decltype(strLen)>(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<OptionSetBase *>(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<char *>(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<char>(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<struct option *>(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<int>(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<char **>(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<size_t>(argCount + 1))
{
argListSize *= 2;
argList = static_cast<char **>(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<const char **>(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