]> git.ipfire.org Git - thirdparty/ipxe.git/commitdiff
[parseopt] Add generic option-parsing library
authorMichael Brown <mcb30@ipxe.org>
Sun, 21 Nov 2010 15:58:30 +0000 (15:58 +0000)
committerMichael Brown <mcb30@ipxe.org>
Sun, 21 Nov 2010 20:38:26 +0000 (20:38 +0000)
Command implementations tend to include a substantial amount of common
boilerplate code revolving around the parsing of command-line options
and arguments.  This increases the size cost of each command.

Introduce an option-parsing library that abstracts out the common
operations involved in command implementations.  This enables the size
of each individual command to be reduced, and also enhances
consistency between commands.

Total size of the library is 704 bytes, to be amortised across all
command implementations.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
src/core/parseopt.c [new file with mode: 0644]
src/include/ipxe/errfile.h
src/include/ipxe/parseopt.h [new file with mode: 0644]

diff --git a/src/core/parseopt.c b/src/core/parseopt.c
new file mode 100644 (file)
index 0000000..ccaed37
--- /dev/null
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2010 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.
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <getopt.h>
+#include <ipxe/netdevice.h>
+#include <ipxe/image.h>
+#include <ipxe/parseopt.h>
+
+/** @file
+ *
+ * Command line option parsing
+ *
+ */
+
+/** Return status code for "--help" option */
+#define ECANCELED_NO_OP __einfo_error ( EINFO_ECANCELED_NO_OP )
+#define EINFO_ECANCELED_NO_OP \
+       __einfo_uniqify ( EINFO_ECANCELED, 0x01, "Nothing to do" )
+
+/**
+* Parse string value
+ *
+ * @v text             Text
+ * @ret value          String value
+ * @ret rc             Return status code
+ */
+int parse_string ( const char *text, const char **value ) {
+
+       /* Sanity check */
+       assert ( text != NULL );
+
+       /* Parse string */
+       *value = text;
+
+       return 0;
+}
+
+/**
+ * Parse integer value
+ *
+ * @v text             Text
+ * @ret value          Integer value
+ * @ret rc             Return status code
+ */
+int parse_integer ( const char *text, unsigned int *value ) {
+       char *endp;
+
+       /* Sanity check */
+       assert ( text != NULL );
+
+       /* Parse integer */
+       *value = strtoul ( text, &endp, 10 );
+       if ( *endp ) {
+               printf ( "\"%s\": invalid integer value\n", text );
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+/**
+ * Parse network device name
+ *
+ * @v text             Text
+ * @ret netdev         Network device
+ * @ret rc             Return status code
+ */
+int parse_netdev ( const char *text, struct net_device **netdev ) {
+
+       /* Sanity check */
+       assert ( text != NULL );
+
+       /* Find network device */
+       *netdev = find_netdev ( text );
+       if ( ! *netdev ) {
+               printf ( "\"%s\": no such network device\n", text );
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
+/**
+ * Parse image name
+ *
+ * @v text             Text
+ * @ret image          Image
+ * @ret rc             Return status code
+ */
+int parse_image ( const char *text, struct image **image ) {
+
+       /* Sanity check */
+       assert ( text != NULL );
+
+       /* Find network device */
+       *image = find_image ( text );
+       if ( ! *image ) {
+               printf ( "\"%s\": no such image\n", text );
+               return -ENOENT;
+       }
+
+       return 0;
+}
+
+/**
+ * Print command usage message
+ *
+ * @v cmd              Command descriptor
+ * @v argv             Argument list
+ */
+void print_usage ( struct command_descriptor *cmd, char **argv ) {
+       printf ( "Usage:\n\n  %s %s\n", argv[0], cmd->usage_description );
+}
+
+/**
+ * Parse command-line options
+ *
+ * @v argc             Argument count
+ * @v argv             Argument list
+ * @v cmd              Command descriptor
+ * @v opts             Options
+ * @ret rc             Return status code
+ */
+int parse_options ( int argc, char **argv, struct command_descriptor *cmd,
+                   void *opts ) {
+       struct option longopts[ cmd->num_options + 1 /* help */ + 1 /* end */ ];
+       char shortopts[ cmd->num_options * 3 /* possible "::" */ + 1 /* "h" */
+                       + 1 /* NUL */ ];
+       unsigned int shortopt_idx = 0;
+       int ( * parse ) ( const char *text, void *value );
+       void *value;
+       unsigned int i;
+       unsigned int j;
+       unsigned int num_args;
+       int c;
+       int rc;
+
+       /* Construct long and short option lists for getopt_long() */
+       memset ( longopts, 0, sizeof ( longopts ) );
+       for ( i = 0 ; i < cmd->num_options ; i++ ) {
+               longopts[i].name = cmd->options[i].longopt;
+               longopts[i].has_arg = cmd->options[i].has_arg;
+               longopts[i].val = cmd->options[i].shortopt;
+               shortopts[shortopt_idx++] = cmd->options[i].shortopt;
+               assert ( cmd->options[i].has_arg <= optional_argument );
+               for ( j = cmd->options[i].has_arg ; j > 0 ; j-- )
+                       shortopts[shortopt_idx++] = ':';
+       }
+       longopts[i].name = "help";
+       longopts[i].val = 'h';
+       shortopts[shortopt_idx++] = 'h';
+       shortopts[shortopt_idx++] = '\0';
+       assert ( shortopt_idx <= sizeof ( shortopts ) );
+       DBGC ( cmd,  "Command \"%s\" has options \"%s\", %d-%d args, len %d\n",
+              argv[0], shortopts, cmd->min_args, cmd->max_args, cmd->len );
+
+       /* Clear options */
+       memset ( opts, 0, cmd->len );
+
+       /* Parse options */
+       while ( ( c = getopt_long ( argc, argv, shortopts, longopts,
+                                   NULL ) ) >= 0 ) {
+               switch ( c ) {
+               case 'h' :
+                       /* Print help */
+                       print_usage ( cmd, argv );
+                       return -ECANCELED_NO_OP;
+               case '?' :
+               case ':' :
+                       /* Print usage message */
+                       print_usage ( cmd, argv );
+                       return -EINVAL;
+               default:
+                       /* Search for an option to parse */
+                       for ( i = 0 ; i < cmd->num_options ; i++ ) {
+                               if ( c != cmd->options[i].shortopt )
+                                       continue;
+                               parse = cmd->options[i].parse;
+                               value = ( opts + cmd->options[i].offset );
+                               if ( ( rc = parse ( optarg, value ) ) != 0 )
+                                       return rc;
+                               break;
+                       }
+                       assert ( i < cmd->num_options );
+               }
+       }
+
+       /* Check remaining arguments */
+       num_args = ( argc - optind );
+       if ( ( num_args < cmd->min_args ) || ( num_args > cmd->max_args ) ) {
+               print_usage ( cmd, argv );
+               return -ERANGE;
+       }
+
+       return 0;
+}
index 5f0f16611021cc18092c7ac01a214adfe48da256..7b415928866e56f97ad60ab63a8398b5dcf86f49 100644 (file)
@@ -59,6 +59,7 @@ FILE_LICENCE ( GPL2_OR_LATER );
 #define ERRFILE_acpi                  ( ERRFILE_CORE | 0x00130000 )
 #define ERRFILE_null_sanboot          ( ERRFILE_CORE | 0x00140000 )
 #define ERRFILE_edd                   ( ERRFILE_CORE | 0x00150000 )
+#define ERRFILE_parseopt              ( ERRFILE_CORE | 0x00160000 )
 
 #define ERRFILE_eisa                ( ERRFILE_DRIVER | 0x00000000 )
 #define ERRFILE_isa                 ( ERRFILE_DRIVER | 0x00010000 )
diff --git a/src/include/ipxe/parseopt.h b/src/include/ipxe/parseopt.h
new file mode 100644 (file)
index 0000000..f949b4c
--- /dev/null
@@ -0,0 +1,127 @@
+#ifndef _IPXE_PARSEOPT_H
+#define _IPXE_PARSEOPT_H
+
+/** @file
+ *
+ * Command line option parsing
+ *
+ */
+
+FILE_LICENCE ( GPL2_OR_LATER );
+
+#include <stdint.h>
+#include <stddef.h>
+
+struct net_device;
+struct image;
+
+/** A command-line option descriptor */
+struct option_descriptor {
+       /** Long option name, if any */
+       const char *longopt;
+       /** Short option name */
+       char shortopt;
+       /** Argument requirement (as for @c struct @c option) */
+       uint8_t has_arg;
+       /** Offset of field within options structure */
+       uint16_t offset;
+       /** Parse option
+        *
+        * @v text              Option text
+        * @v value             Option value to fill in
+        * @ret rc              Return status code
+        */
+       int ( * parse ) ( const char *text, void *value );
+};
+
+/**
+ * Construct option parser
+ *
+ * @v _struct          Options structure type
+ * @v _field           Field within options structure
+ * @v _parse           Field type-specific option parser
+ * @ret _parse         Generic option parser
+ */
+#define OPTION_PARSER( _struct, _field, _parse )                             \
+       ( ( int ( * ) ( const char *text, void *value ) )                     \
+         ( ( ( ( typeof ( _parse ) * ) NULL ) ==                             \
+             ( ( int ( * ) ( const char *text,                               \
+                             typeof ( ( ( _struct * ) NULL )->_field ) * ) ) \
+               NULL ) ) ? _parse : _parse ) )
+
+/**
+ * Construct option descriptor
+ *
+ * @v _longopt         Long option name, if any
+ * @v _shortopt                Short option name, if any
+ * @v _has_arg         Argument requirement
+ * @v _struct          Options structure type
+ * @v _field           Field within options structure
+ * @v _parse           Field type-specific option parser
+ * @ret _option                Option descriptor
+ */
+#define OPTION_DESC( _longopt, _shortopt, _has_arg, _struct, _field, _parse ) \
+       {                                                                     \
+               .longopt = _longopt,                                          \
+               .shortopt = _shortopt,                                        \
+               .has_arg = _has_arg,                                          \
+               .offset = offsetof ( _struct, _field ),                       \
+               .parse = OPTION_PARSER ( _struct, _field, _parse ),           \
+       }
+
+/** A command descriptor */
+struct command_descriptor {
+       /** Option descriptors */
+       struct option_descriptor *options;
+       /** Number of option descriptors */
+       uint8_t num_options;
+       /** Length of option structure */
+       uint8_t len;
+       /** Minimum number of non-option arguments */
+       uint8_t min_args;
+       /** Maximum number of non-option arguments */
+       uint8_t max_args;
+       /** Command usage and description
+        *
+        * This excludes the literal "Usage:" and the command name,
+        * which will be prepended automatically.
+        */
+       const char *usage_description;
+};
+
+/** No maximum number of arguments */
+#define MAX_ARGUMENTS 0xff
+
+/**
+ * Construct command descriptor
+ *
+ * @v _struct          Options structure type
+ * @v _options         Option descriptor array
+ * @v _check_args      Remaining argument checker
+ * @v _usage           Command usage
+ * @v _description     Command description
+ * @ret _command       Command descriptor
+ */
+#define COMMAND_DESC( _struct, _options, _min_args, _max_args, _usage,       \
+                     _description )                                          \
+       {                                                                     \
+               .options = ( ( ( ( typeof ( _options[0] ) * ) NULL ) ==       \
+                              ( ( struct option_descriptor * ) NULL ) ) ?    \
+                            _options : _options ),                           \
+               .num_options = ( sizeof ( _options ) /                        \
+                                sizeof ( _options[0] ) ),                    \
+               .len = sizeof ( _struct ),                                    \
+               .min_args = _min_args,                                        \
+               .max_args = _max_args,                                        \
+               .usage_description = _usage "\n\n" _description,              \
+        }
+
+extern int parse_string ( const char *text, const char **value );
+extern int parse_integer ( const char *text, unsigned int *value );
+extern int parse_netdev ( const char *text, struct net_device **netdev );
+extern int parse_image ( const char *text, struct image **image );
+extern void print_usage ( struct command_descriptor *cmd, char **argv );
+extern int parse_options ( int argc, char **argv,
+                          struct command_descriptor *cmd, void *opts );
+
+#endif /* _IPXE_PARSEOPT_H */