diff --git a/src/core/parseopt.c b/src/core/parseopt.c new file mode 100644 index 00000000..ccaed374 --- /dev/null +++ b/src/core/parseopt.c @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2010 Michael Brown . + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @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; +} diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 5f0f1661..7b415928 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -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 index 00000000..f949b4cc --- /dev/null +++ b/src/include/ipxe/parseopt.h @@ -0,0 +1,127 @@ +#ifndef _IPXE_PARSEOPT_H +#define _IPXE_PARSEOPT_H + +/** @file + * + * Command line option parsing + * + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include +#include + +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 */