nuttx/libs/libc/unistd/lib_getopt_common.c
Gregory Nutt a0f9da20a2 getopt(): Update comments about missing functionality
Per the Linux man page, "By default, getopt() permutes the contents of argv as it scans, so that eventually all the nonoptions are at the end."  This behavior, however, is not implemented in the NuttX getopt() logic.
2021-04-04 22:31:47 +01:00

647 lines
20 KiB
C

/****************************************************************************
* libs/libc/unistd/lib_getopt_common.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you 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.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <stdbool.h>
#include <string.h>
#include "unistd.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#undef optarg
#undef opterr
#undef optind
#undef optopt
/****************************************************************************
* Prive Functions
****************************************************************************/
/****************************************************************************
* Name: compare_long_option
*
* Description:
* Compare a command argument with a long option, handling the cases:
*
* --option: Any argument is in the next argv entry
* --option=argument: The argument is in the same argv entry
*
****************************************************************************/
static int compare_long_option(FAR const char *cmdarg,
FAR const char *optname,
FAR const char **argument)
{
int result;
*argument = NULL;
for (; ; )
{
int rawchar = *cmdarg++;
int optchar = *optname++;
int cmdchar;
/* The command line option may terminate with either '\0' or '='. */
cmdchar = rawchar;
if (cmdchar == '=')
{
cmdchar = '\0';
}
/* Perform the comparison */
result = cmdchar - optchar;
if (result != 0 || cmdchar == '\0')
{
/* If the '=' is the real terminator, then return the argument
* that follows the '='
*/
if (rawchar == '=')
{
*argument = cmdarg;
}
break;
}
}
return result;
}
/****************************************************************************
* Name: getopt_long_option
*
* Description:
* Handle one long option
*
****************************************************************************/
static int getopt_long_option(FAR struct getopt_s *go,
FAR char * const argv[],
FAR const struct option *longopts,
FAR int *longindex)
{
int ndx;
int ret;
/* The option list may not be NULL in this context */
if (longopts == NULL)
{
goto errout;
}
/* Search the list of long options for a matching name.
* The last element of the option arry must be filled with zeroes.
*/
for (ndx = 0; longopts[ndx].name != NULL; ndx++)
{
FAR char *terminator = NULL;
if (compare_long_option(go->go_optptr, longopts[ndx].name,
(FAR const char **)&terminator) == 0)
{
/* Found the option with the matching name. Does it have an
* argument provided in the same argv entry like
* --option=argument?
*/
if (terminator != NULL)
{
/* Skip over the option + argument */
go->go_optptr = NULL;
go->go_optind++;
switch (longopts[ndx].has_arg)
{
case no_argument:
/* But no argument is expected! */
go->go_optarg = NULL;
return '?';
case optional_argument:
case required_argument:
/* Return the required argument */
go->go_optarg = terminator;
break;
default:
goto errout;
break;
}
}
else
{
/* Does the option have a required argument in the next argv
* entry? An optional argument?
*/
switch (longopts[ndx].has_arg)
{
FAR char *next;
case no_argument:
/* No, no arguments. Just return the argument that we
* found.
*/
go->go_optptr = NULL;
go->go_optind++;
break;
case optional_argument:
/* Check if there is a following argument and if that
* following argument is another option.
*/
next = argv[go->go_optind + 1];
if (next == NULL || next[0] == '-')
{
go->go_optptr = NULL;
go->go_optarg = NULL;
go->go_optind++;
break;
}
/* Fall through and treat as a required option */
case required_argument:
/* Verify that the required argument is present */
next = argv[go->go_optind + 1];
if (next == NULL || next[0] == '-')
{
go->go_optptr = NULL;
go->go_optarg = NULL;
go->go_optind++;
return '?';
}
/* Return the required argument */
go->go_optptr = NULL;
go->go_optarg = next;
go->go_optind += 2;
break;
default:
goto errout;
break;
}
}
/* Setup return value.
*
* 'val' is the value to return on success, or to load into the
* variable pointed to by flag.
*
* 'flag' specifies how results are returned for a long option. If
* flag is NULL, then getopt_long() returns val. Otherwise,
* getopt_long() returns 0, and flag points to a variable which is
* set to val if the option is found, but left unchanged if the
* option is not found.
*/
if (longopts[ndx].flag != NULL)
{
*(longopts[ndx].flag) = longopts[ndx].val;
ret = OK;
}
else
{
ret = longopts[ndx].val;
}
/* If longindex is not NULL, it points to a variable which is
* set to the index of the long option relative to longopts.
*/
if (longindex != NULL)
{
*longindex = ndx;
}
return ret;
}
}
/* This option is not in the list of valid options */
go->go_optopt = *go->go_optptr;
return '?';
errout:
/* Restore the initial, uninitialized state */
go->go_binitialized = false;
return ERROR;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: getopt_common
*
* Description:
*
* getopt_common() is the common, internal implementation of getopt(),
* getopt_long(), and getopt_long_only().
*
* getopt() parses command-line arguments. Its arguments argc and argv
* are the argument count and array as passed to the main() function on
* program invocation. An element of argv that starts with '-' is an
* option element. The characters of this element (aside from the initial
* '-') are option characters. If getopt() is called repeatedly, it
* returns successively each of the option characters from each of the
* option elements.
*
* If getopt() finds another option character, it returns that character,
* updating the external variable optind and a static variable nextchar so
* that the next call to getopt() can resume the scan with the following
* option character or argv-element.
*
* If there are no more option characters, getopt() returns -1. Then optind
* is the index in argv of the first argv-element that is not an option.
*
* The 'optstring' argument is a string containing the legitimate option
* characters. If such a character is followed by a colon, this indicates
* that the option requires an argument. If an argument is required for an
* option so getopt() places a pointer to the following text in the same
* argv-element, or the text of the following argv-element, in optarg.
*
* The getopt_long() function works like getopt() except that it also
* accepts long options, started with two dashes. (If the program accepts
* only long options, then optstring should be specified as an empty
* string (""), not NULL.) Long option names may be abbreviated if the
* abbreviation is unique or is an exact match for some defined option
*
* getopt_long_only() is like getopt_long(), but '-' as well as "--" can
* indicate a long option. If an option that starts with '-' (not "--")
* doesn't match a long option, but does match a short option, it is
* parsed as a short option instead.
*
* NOTES:
* 1. opterr is not supported and this implementation of getopt() never
* printfs error messages.
* 2. getopt is NOT threadsafe!
* 3. This version of getopt() does not reset global variables until
* -1 is returned. As a result, your command line parsing loops
* must call getopt() repeatedly and continue to parse if other
* errors are returned ('?' or ':') until getopt() finally returns -1.
* (You can also set optind to -1 to force a reset).
* 4. Standard getopt() permutes the contents of argv as it scans, so that
* eventually all the nonoptions are at the end. This implementation
* does not do this.
*
* Returned Value:
* If an option was successfully found, then getopt() returns the option
* character. If all command-line options have been parsed, then getopt()
* returns -1. If getopt() encounters an option character that was not
* in optstring, then '?' is returned. If getopt() encounters an option
* with a missing argument, then the return value depends on the first
* character in optstring: if it is ':', then ':' is returned; otherwise
* '?' is returned.
*
****************************************************************************/
int getopt_common(int argc, FAR char * const argv[],
FAR const char *optstring,
FAR const struct option *longopts,
FAR int *longindex,
enum getopt_mode_e mode)
{
int ret;
/* Get thread-specific getopt() variables */
FAR struct getopt_s *go = getoptvars();
if (go == NULL)
{
return '?';
}
/* Verify input parameters. */
if (argv != NULL)
{
FAR char *optchar;
int noarg_ret = '?';
/* The initial value of optind is 1. If getopt() is called again in
* the program, optind must be reset to some value <= 1.
*/
if (go->go_optind < 1 || !go->go_binitialized)
{
go->go_optarg = NULL;
go->go_optind = 1; /* Skip over the program name */
go->go_optopt = '?';
go->go_optptr = NULL; /* Start at the beginning of the first argument */
go->go_binitialized = true; /* Now we are initialized */
}
/* Are we resuming in the middle, or at the end of a string of
* arguments? optptr == NULL means that we are started at the
* beginning of argv[optind]; *optptr == \0 means that we are
* starting at the beginning of optind+1
*/
while (!go->go_optptr || !*go->go_optptr)
{
/* We need to start at the beginning of the next argv. Check if we
* need to increment optind
*/
if (go->go_optptr)
{
/* Yes.. Increment it and check for the case where where we
* have processed everything in the argv[] array.
*/
go->go_optind++;
}
/* Check for the end of the argument list */
go->go_optptr = argv[go->go_optind];
if (!go->go_optptr)
{
/* There are no more arguments, we are finished */
go->go_binitialized = false;
return ERROR;
}
/* We are starting at the beginning of argv[optind]. In this case,
* the first character must be '-'
*/
if (*go->go_optptr != '-')
{
/* The argument does not start with '-', we are finished */
go->go_binitialized = false;
return ERROR;
}
/* Skip over the '-' */
go->go_optptr++;
}
/* Special case handling of "-" and "-:" */
if (!*go->go_optptr)
{
/* We'll fix up optptr the next time we are called */
go->go_optopt = '\0';
return '?';
}
/* Handle the case of "-:" */
if (*go->go_optptr == ':')
{
go->go_optopt = ':';
go->go_optptr++;
return '?';
}
/* go->go_optptr now points at the next option and it is not something
* crazy. Possibilities:
*
* FORM APPLICABILITY
* -o getopt(), getopt_long_only()
* -o reqarg getopt(), getopt_long_only()
* -o optarg getopt_long_only()
* -option getopt_long_only()
* -option reqarg getopt_long_only()
* -option optarg getopt_long_only()
* --option getopt_long(), getopt_long_only()
* --option reqarg getopt_long(), getopt_long_only()
* --option optarg getopt_long(), getopt_long_only()
*
* Where:
* o - Some short option
* option - Some long option
* reqarg - A required argument
* optarg - An optional argument
*/
/* Check for --option forms or -option forms */
if (GETOPT_HAVE_LONG(mode))
{
/* Handle -option and --option forms. */
if (*go->go_optptr == '-')
{
/* Skip over the second '-' */
go->go_optptr++;
/* And parse the long option */
ret = getopt_long_option(go, argv, longopts, longindex);
if (ret == '?')
{
/* Skip over the unrecognized long option. */
go->go_optind++;
go->go_optptr = NULL;
}
return ret;
}
/* The -option form is only valid in getop_long_only() mode and
* must be distinguished from the -o case forms.
*/
else if (GETOPT_HAVE_LONGONLY(mode))
{
/* A special case is that the option is of a form like
* -o but is represented as a single character long option.
* In that case, getopt_long_option() will fail with '?' and,
* if it is a single character option, we can just fall
* through to the short option logic.
*/
ret = getopt_long_option(go, argv, longopts, longindex);
if (ret != '?')
{
/* Return success or ERROR */
return ret;
}
/* Check for single character option.
*
* REVISIT: There is no way to distinguish a sequence of
* short arguments like -abc (meaning -a -b -c) from a single
* long argument (like "abc"). I am not sure of the correct
* behavior in this case. While supported for getopt(), I do
* not think that the first interpretation is standard.
*/
else if (*(go->go_optptr + 1) != '\0')
{
/* Skip over the unrecognized long option. */
go->go_optind++;
go->go_optptr = NULL;
return ret;
}
}
}
/* Check if the option is in the list of valid short options.
* In long option modes, opstring may be NULL. However, that is
* an error in any case here because we have not found any
* long options.
*/
if (optstring == NULL)
{
/* Not an error with getopt_long() */
if (GETOPT_HAVE_LONG(mode))
{
/* Return '?'. optptr is reset to the next argv entry,
* discarding everything else that follows in the argv string
* (which could be another single character command).
*/
go->go_optopt = *go->go_optptr;
go->go_optptr = NULL;
go->go_optind++;
return '?';
}
else
{
/* Restore the initial, uninitialized state, and return an
* error.
*/
go->go_binitialized = false;
return ERROR;
}
}
/* If the first character of opstring s ':', then ':' is in the event
* of a missing argument. Otherwise '?' is returned.
*/
if (*optstring == ':')
{
noarg_ret = ':';
optstring++;
}
/* Check if the option appears in 'optstring' */
optchar = strchr(optstring, *go->go_optptr);
if (!optchar)
{
/* No this character is not in the list of valid options */
go->go_optopt = *go->go_optptr;
go->go_optptr++;
return '?';
}
/* Yes, the character is in the list of valid options. Does it have a
* required argument?
*/
if (optchar[1] != ':')
{
/* No, no arguments. Just return the character that we found */
go->go_optptr++;
return *optchar;
}
/* Yes, it may have an argument. Is the argument immediately after
* the command in this same argument?
*/
if (go->go_optptr[1] != '\0')
{
/* Yes, return a pointer into the current argument */
go->go_optarg = &go->go_optptr[1];
go->go_optind++;
go->go_optptr = NULL;
return *optchar;
}
/* No.. is there an argument in the next value of argv[] ? */
if (argv[go->go_optind + 1] && *argv[go->go_optind + 1] != '-')
{
/* Yes.. return that */
go->go_optarg = argv[go->go_optind + 1];
go->go_optind += 2;
go->go_optptr = NULL;
return *optchar;
}
/* No argument was supplied */
go->go_optptr = NULL;
go->go_optarg = NULL;
go->go_optopt = *optchar;
go->go_optind++;
/* Two colons means that the argument is optional. */
return (optchar[2] == ':') ? *optchar : noarg_ret;
}
/* Restore the initial, uninitialized state, and return an error. */
go->go_binitialized = false;
return ERROR;
}