text: /****************************************************************************
   * 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"
  
  /****************************************************************************
   * 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;
  }
  