Added a functional version of getopt() and getopt_long(), ready for use
authorMichael Brown <mcb30@etherboot.org>
Thu, 7 Dec 2006 03:54:57 +0000 (03:54 +0000)
committerMichael Brown <mcb30@etherboot.org>
Thu, 7 Dec 2006 03:54:57 +0000 (03:54 +0000)
in our commands.

src/core/getopt.c [new file with mode: 0644]
src/include/getopt.h [new file with mode: 0644]

diff --git a/src/core/getopt.c b/src/core/getopt.c
new file mode 100644 (file)
index 0000000..df27553
--- /dev/null
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <stdint.h>
+#include <string.h>
+#include <vsprintf.h>
+#include <getopt.h>
+
+/** @file
+ *
+ * Parse command-line options
+ *
+ */
+
+/**
+ * Option argument
+ *
+ * This will point to the argument for the most recently returned
+ * option, if applicable.
+ */
+char *optarg;
+
+/**
+ * Current option index
+ *
+ * This is an index into the argv[] array.  When getopt() returns -1,
+ * @c optind is the index to the first element that is not an option.
+ */
+int optind = 1;
+
+/**
+ * Current option character index
+ *
+ * This is an index into the current element of argv[].
+ */
+static int nextchar = 0;
+
+/**
+ * Unrecognised option
+ *
+ * When an unrecognised option is encountered, the actual option
+ * character is stored in @c optopt.
+ */
+int optopt;
+
+/**
+ * Reset getopt() internal state
+ *
+ * Due to a limitation of the POSIX getopt() API, it is necessary to
+ * add a call to reset_getopt() before each set of calls to getopt()
+ * or getopt_long().  This arises because POSIX assumes that each
+ * process will parse command line arguments no more than once; this
+ * assumption is not valid within Etherboot.  We work around the
+ * limitation by arranging for execv() to call reset_getopt() before
+ * executing the command.
+ */
+void reset_getopt ( void ) {
+       optind = 1;
+       nextchar = 0;
+}
+
+/**
+ * Get option argument from argv[] array
+ *
+ * @v argc             Argument count
+ * @v argv             Argument list
+ * @ret argument       Option argument, or NULL
+ *
+ * Grab the next element of argv[], if it exists and is not an option.
+ */
+static const char * get_argv_argument ( int argc, char * const argv[] ) {
+       char *arg;
+
+       /* Don't overrun argv[] */
+       if ( optind >= argc )
+               return NULL;
+       arg = argv[optind];
+
+       /* If next argv element is an option, then it's not usable as
+        * an argument.
+        */
+       if ( *arg == '-' )
+               return NULL;
+
+       /** Consume this argv element, and return it */
+       optind++;
+       return arg;
+}
+
+/**
+ * Match long option
+ *
+ * @v argc             Argument count
+ * @v argv             Argument list
+ * @v opttext          Option text within current argv[] element
+ * @v longopt          Long option specification
+ * @ret option         Option to return from getopt()
+ * @ret matched                Found a match for this long option
+ */
+static int match_long_option ( int argc, char * const argv[],
+                              const char *opttext,
+                              const struct option *longopt, int *option ) {
+       size_t optlen;
+       const char *argument = NULL;
+
+       /* Compare option name */
+       optlen = strlen ( longopt->name );
+       if ( strncmp ( opttext, longopt->name, optlen ) != 0 )
+               return 0;
+
+       /* Check for inline argument */
+       if ( opttext[optlen] == '=' ) {
+               argument = &opttext[ optlen + 1 ];
+       } else if ( opttext[optlen] ) {
+               /* Long option with trailing garbage - no match */
+               return 0;
+       }
+
+       /* Consume this argv element */
+       optind++;
+
+       /* If we want an argument but don't have one yet, try to grab
+        * the next argv element
+        */
+       if ( ( longopt->has_arg != no_argument ) && ( ! argument ) )
+               argument = get_argv_argument ( argc, argv );
+
+       /* If we need an argument but don't have one, sulk */
+       if ( ( longopt->has_arg == required_argument ) && ( ! argument ) ) {
+               printf ( "Option \"%s\" requires an argument\n",
+                        longopt->name );
+               *option = ':';
+               return 1;
+       }
+
+       /* If we have an argument where we shouldn't have one, sulk */
+       if ( ( longopt->has_arg == no_argument ) && argument ) {
+               printf ( "Option \"%s\" takes no argument\n", longopt->name );
+               *option = ':';
+               return 1;
+       }
+
+       /* Store values and return success */
+       optarg = ( char * ) argument;
+       if ( longopt->flag ) {
+               *(longopt->flag) = longopt->val;
+               *option = 0;
+       } else {
+               *option = longopt->val;
+       }
+       return 1;
+}
+
+/**
+ * Match short option
+ *
+ * @v argc             Argument count
+ * @v argv             Argument list
+ * @v opttext          Option text within current argv[] element
+ * @v shortopt         Option character from option specification
+ * @ret option         Option to return from getopt()
+ * @ret matched                Found a match for this short option
+ */
+static int match_short_option ( int argc, char * const argv[],
+                               const char *opttext, int shortopt,
+                               enum getopt_argument_requirement has_arg,
+                               int *option ) {
+       const char *argument = NULL;
+
+       /* Compare option character */
+       if ( *opttext != shortopt )
+               return 0;
+
+       /* Consume option character */
+       opttext++;
+       nextchar++;
+       if ( *opttext ) {
+               if ( has_arg != no_argument ) {
+                       /* Consume remainder of element as inline argument */
+                       argument = opttext;
+                       optind++;
+                       nextchar = 0;
+               }
+       } else {
+               /* Reached end of argv element */
+               optind++;
+               nextchar = 0;
+       }
+
+       /* If we want an argument but don't have one yet, try to grab
+        * the next argv element
+        */
+       if ( ( has_arg != no_argument ) && ( ! argument ) )
+               argument = get_argv_argument ( argc, argv );
+
+       /* If we need an argument but don't have one, sulk */
+       if ( ( has_arg == required_argument ) && ( ! argument ) ) {
+               printf ( "Option \"%c\" requires an argument\n", shortopt );
+               *option = ':';
+               return 1;
+       }
+
+       /* Store values and return success */
+       optarg = ( char * ) argument;
+       *option = shortopt;
+       return 1;
+}
+
+/**
+ * Parse command-line options
+ *
+ * @v argc             Argument count
+ * @v argv             Argument list
+ * @v optstring                Option specification string
+ * @v longopts         Long option specification table
+ * @ret longindex      Index of long option (or NULL)
+ * @ret option         Option found, or -1 for no more options
+ *
+ */
+int getopt_long ( int argc, char * const argv[], const char *optstring,
+                 const struct option *longopts, int *longindex ) {
+       const char *opttext = argv[optind];
+       const struct option *longopt;
+       int shortopt;
+       enum getopt_argument_requirement has_arg;
+       int option;
+
+       /* Check for end of options */
+       if ( *(opttext++) != '-' )
+               return -1;
+
+       /* Check for long options */
+       if ( *(opttext++) == '-' ) {
+               for ( longopt = longopts ; longopt->name ; longopt++ ) {
+                       if ( ! match_long_option ( argc, argv, opttext,
+                                                  longopt, &option ) )
+                               continue;
+                       if ( longindex )
+                               *longindex = ( longopt - longopts );
+                       return option;
+               }
+               optopt = '?';
+               printf ( "Unrecognised option \"--%s\"\n", opttext );
+               return '?';
+       }
+
+       /* Check for short options */
+       if ( nextchar < 1 )
+               nextchar = 1;
+       opttext = ( argv[optind] + nextchar );
+       while ( ( shortopt = *(optstring++) ) ) {
+               has_arg = no_argument;
+               while ( *optstring == ':' ) {
+                       has_arg++;
+                       optstring++;
+               }
+               if ( match_short_option ( argc, argv, opttext, shortopt,
+                                         has_arg, &option ) ) {
+                       return option;
+               }
+       }
+       optopt = *opttext;
+       printf ( "Unrecognised option \"-%c\"\n", optopt );
+       return '?';
+}
diff --git a/src/include/getopt.h b/src/include/getopt.h
new file mode 100644 (file)
index 0000000..e9d3d64
--- /dev/null
@@ -0,0 +1,75 @@
+#ifndef _GETOPT_H
+#define _GETOPT_H
+
+/** @file
+ *
+ * Parse command-line options
+ *
+ */
+
+#include <stddef.h>
+
+enum getopt_argument_requirement {
+       /** Option does not take an argument */
+       no_argument = 0,
+       /** Option requires an argument */
+       required_argument = 1,
+       /** Option may have an argument */
+       optional_argument = 2,
+};
+
+/** A long option, as used for getopt_long() */
+struct option {
+       /** Long name of this option */
+       const char *name;
+       /** Option takes an argument
+        *
+        * Must be one of @c no_argument, @c required_argument, or @c
+        * optional_argument.
+        */
+       int has_arg;
+       /** Location into which to store @c val, or NULL.
+        *
+        * See the description for @c val for more details.
+        */
+       int *flag;
+       /** Value to return
+        *
+        * If @c flag is NULL, then this is the value that will be
+        * returned by getopt_long() when this option is found, and
+        * should therefore be set to the equivalent short option
+        * character.
+        *
+        * If @c flag is non-NULL, then this value will be written to
+        * the location pointed to by @flag, and getopt_long() will
+        * return 0.
+        */
+       int val;
+};
+
+extern char *optarg;
+extern int optind;
+extern int optopt;
+
+extern int getopt_long ( int argc, char * const argv[], const char *optstring,
+                        const struct option *longopts, int *longindex );
+
+/**
+ * Parse command-line options
+ *
+ * @v argv             Argument count
+ * @v argv             Argument list
+ * @v optstring                Option specification string
+ * @ret option         Option found, or -1 for no more options
+ *
+ * See getopt_long() for full details.
+ */
+static inline int getopt ( int argc, char * const argv[],
+                          const char *optstring ) {
+       static const struct option no_options[] = {
+               { NULL, 0, NULL, 0 }
+       };
+       return getopt_long ( argc, argv, optstring, no_options, NULL );
+}
+
+#endif /* _GETOPT_H */