From e5c38390735e54b0b4fec2429e30e57d956b7af1 Mon Sep 17 00:00:00 2001 From: David Schweikert Date: Thu, 9 Feb 2017 12:19:27 +0100 Subject: [PATCH] integrate optparse (https://github.com/skeeto/optparse) --- ChangeLog | 3 + configure.ac | 1 + src/Makefile.am | 2 +- src/fping.c | 62 ++++++------ src/optparse.c | 264 ++++++++++++++++++++++++++++++++++++++++++++++++ src/optparse.h | 102 +++++++++++++++++++ src/test-c89.sh | 5 - 7 files changed, 401 insertions(+), 38 deletions(-) create mode 100644 src/optparse.c create mode 100644 src/optparse.h delete mode 100755 src/test-c89.sh diff --git a/ChangeLog b/ChangeLog index aa657ac..efa9fe0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,9 @@ Unreleased * (feature) --enable-ipv6 is now default * (feature) New option '-4' to force IPv4 * (feature) New option '-6' to force IPv6 + * A C99 compiler is now required + * Option parsing with optparse (https://github.com/skeeto/optparse) + Thanks Christopher Wellons! 2017-02-09 David Schweikert * Version 3.16 diff --git a/configure.ac b/configure.ac index 94a6db7..8b0c4ff 100644 --- a/configure.ac +++ b/configure.ac @@ -52,6 +52,7 @@ dnl Checks for programs. AC_PROG_CC AM_PROG_CC_C_O +AC_PROG_CC_STDC AC_PROG_CPP AC_PROG_INSTALL diff --git a/src/Makefile.am b/src/Makefile.am index c518d34..c58e474 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -2,7 +2,7 @@ AM_CFLAGS = -Wall -Wextra -Wno-sign-compare sbin_PROGRAMS = fping -fping_SOURCES = fping.c seqmap.c socket4.c fping.h options.h seqmap.h +fping_SOURCES = fping.c seqmap.c socket4.c fping.h options.h seqmap.h optparse.c optparse.h fping_DEPENDENCIES = ../config.h if IPV6 diff --git a/src/fping.c b/src/fping.c index f00a13e..26b6ea0 100644 --- a/src/fping.c +++ b/src/fping.c @@ -36,8 +36,7 @@ extern "C" { #include "fping.h" #include "options.h" - -/*** autoconf includes ***/ +#include "optparse.h" #include #include @@ -79,8 +78,6 @@ extern "C" { #include #include -#include - #include /*** externals ***/ @@ -348,8 +345,7 @@ int main(int argc, char** argv) uid_t uid; int tos = 0; HOST_ENTRY* cursor; - - prog = argv[0]; + struct optparse optparse_state; socket4 = open_ping_socket_ipv4(ping_data_size); #ifdef IPV6 @@ -362,6 +358,8 @@ int main(int argc, char** argv) perror("cannot setuid"); } + prog = argv[0]; + optparse_init(&optparse_state, argv); ident = getpid() & 0xFFFF; verbose_flag = 1; backoff_flag = 1; @@ -369,7 +367,7 @@ int main(int argc, char** argv) /* get command line options */ - while ((c = getopt(argc, argv, "46ADMNRadeghlmnoqsuvzB:C:H:I:O:Q:S:T:b:c:f:i:p:r:t:")) != EOF) { + while ((c = optparse(&optparse_state, "46ADMNRadeghlmnoqsuvzB:C:H:I:O:Q:S:T:b:c:f:i:p:r:t:")) != EOF) { switch (c) { case '4': if (hints_ai_family != AF_UNSPEC) { @@ -413,37 +411,37 @@ int main(int argc, char** argv) break; case 't': - if (!(timeout = (unsigned int)atoi(optarg) * 100)) + if (!(timeout = (unsigned int)atoi(optparse_state.optarg) * 100)) usage(1); break; case 'r': - if (!sscanf(optarg, "%u", &retry)) + if (!sscanf(optparse_state.optarg, "%u", &retry)) usage(1); break; case 'i': - if (!sscanf(optarg, "%u", &interval)) + if (!sscanf(optparse_state.optarg, "%u", &interval)) usage(1); interval *= 100; break; case 'p': - if (!(perhost_interval = (unsigned int)atoi(optarg) * 100)) + if (!(perhost_interval = (unsigned int)atoi(optparse_state.optarg) * 100)) usage(1); break; case 'c': - if (!(count = (unsigned int)atoi(optarg))) + if (!(count = (unsigned int)atoi(optparse_state.optarg))) usage(1); count_flag = 1; break; case 'C': - if (!(count = (unsigned int)atoi(optarg))) + if (!(count = (unsigned int)atoi(optparse_state.optarg))) usage(1); count_flag = 1; @@ -451,7 +449,7 @@ int main(int argc, char** argv) break; case 'b': - if (!sscanf(optarg, "%u", &ping_data_size)) + if (!sscanf(optparse_state.optarg, "%u", &ping_data_size)) usage(1); break; @@ -468,7 +466,7 @@ int main(int argc, char** argv) case 'Q': verbose_flag = 0; quiet_flag = 1; - if (!(report_interval = (unsigned int)atoi(optarg) * 100000)) + if (!(report_interval = (unsigned int)atoi(optparse_state.optarg) * 100000)) usage(1); break; @@ -495,7 +493,7 @@ int main(int argc, char** argv) break; case 'B': - if (!(backoff = atof(optarg))) + if (!(backoff = atof(optparse_state.optarg))) usage(1); break; @@ -526,25 +524,25 @@ int main(int argc, char** argv) break; case 'H': - if (!(ttl = (u_int)atoi(optarg))) + if (!(ttl = (u_int)atoi(optparse_state.optarg))) usage(1); break; #if defined(DEBUG) || defined(_DEBUG) case 'z': - if (!(debugging = (unsigned int)atoi(optarg))) + if (!(debugging = (unsigned int)atoi(optparse_state.optarg))) usage(1); break; #endif /* DEBUG || _DEBUG */ case 'v': - printf("%s: Version %s\n", argv[0], VERSION); - printf("%s: comments to %s\n", argv[0], EMAIL); + printf("%s: Version %s\n", prog, VERSION); + printf("%s: comments to %s\n", prog, EMAIL); exit(0); case 'f': - filename = optarg; + filename = optparse_state.optarg; break; case 'g': @@ -554,12 +552,12 @@ int main(int argc, char** argv) break; case 'S': - if (inet_pton(AF_INET, optarg, &src_addr)) { + if (inet_pton(AF_INET, optparse_state.optarg, &src_addr)) { src_addr_set = 1; break; } #ifdef IPV6 - if (inet_pton(AF_INET6, optarg, &src_addr6)) { + if (inet_pton(AF_INET6, optparse_state.optarg, &src_addr6)) { src_addr6_set = 1; break; } @@ -570,19 +568,19 @@ int main(int argc, char** argv) case 'I': #ifdef SO_BINDTODEVICE if (socket4) { - if (setsockopt(socket4, SOL_SOCKET, SO_BINDTODEVICE, optarg, strlen(optarg))) { + if (setsockopt(socket4, SOL_SOCKET, SO_BINDTODEVICE, optparse_state.optarg, strlen(optparse_state.optarg))) { perror("binding to specific interface (SO_BINTODEVICE)"); } } #ifdef IPV6 if (socket6) { - if (setsockopt(socket6, SOL_SOCKET, SO_BINDTODEVICE, optarg, strlen(optarg))) { + if (setsockopt(socket6, SOL_SOCKET, SO_BINDTODEVICE, optparse_state.optarg, strlen(optparse_state.optarg))) { perror("binding to specific interface (SO_BINTODEVICE), IPV6"); } } #endif #else - printf("%s: cant bind to a particular net interface since SO_BINDTODEVICE is not supported on your os.\n", argv[0]); + printf("%s: cant bind to a particular net interface since SO_BINDTODEVICE is not supported on your os.\n", prog); exit(3); ; #endif @@ -593,7 +591,7 @@ int main(int argc, char** argv) break; case 'O': - if (sscanf(optarg, "%i", &tos)) { + if (sscanf(optparse_state.optarg, "%i", &tos)) { if (socket4) { if (setsockopt(socket4, IPPROTO_IP, IP_TOS, &tos, sizeof(tos))) { perror("setting type of service octet IP_TOS"); @@ -615,16 +613,16 @@ int main(int argc, char** argv) outage_flag = 1; break; - default: + case '?': + fprintf(stderr, "%s: %s\n", argv[0], optparse_state.errmsg); fprintf(stderr, "see 'fping -h' for usage information\n"); exit(1); break; - } } /* if we are called 'fping6', assume '-6' */ - if (strstr(argv[0], "fping6")) { + if (strstr(prog, "fping6")) { hints_ai_family = AF_INET6; } @@ -793,8 +791,8 @@ int main(int argc, char** argv) /* handle host names supplied on command line or in a file */ /* if the generate_flag is on, then generate the IP list */ - argv = &argv[optind]; - argc -= optind; + argv = &argv[optparse_state.optind]; + argc -= optparse_state.optind; /* cover allowable conditions */ diff --git a/src/optparse.c b/src/optparse.c new file mode 100644 index 0000000..4242bff --- /dev/null +++ b/src/optparse.c @@ -0,0 +1,264 @@ +#include "optparse.h" + +#define MSG_INVALID "invalid option" +#define MSG_MISSING "option requires an argument" +#define MSG_TOOMANY "option takes no arguments" + +static int +opterror(struct optparse *options, const char *message, const char *data) +{ + unsigned p = 0; + while (*message) + options->errmsg[p++] = *message++; + const char *sep = " -- '"; + while (*sep) + options->errmsg[p++] = *sep++; + while (p < sizeof(options->errmsg) - 2 && *data) + options->errmsg[p++] = *data++; + options->errmsg[p++] = '\''; + options->errmsg[p++] = '\0'; + return '?'; +} + +void optparse_init(struct optparse *options, char **argv) +{ + options->argv = argv; + options->permute = 1; + options->optind = 1; + options->subopt = 0; + options->optarg = 0; + options->errmsg[0] = '\0'; +} + +static inline int +is_dashdash(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; +} + +static inline int +is_shortopt(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; +} + +static inline int +is_longopt(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; +} + +static void +permute(struct optparse *options, int index) +{ + char *nonoption = options->argv[index]; + for (int i = index; i < options->optind - 1; i++) + options->argv[i] = options->argv[i + 1]; + options->argv[options->optind - 1] = nonoption; +} + +static int +argtype(const char *optstring, char c) +{ + if (c == ':') + return -1; + for (; *optstring && c != *optstring; optstring++); + if (!*optstring) + return -1; + int count = OPTPARSE_NONE; + if (optstring[1] == ':') + count += optstring[2] == ':' ? 2 : 1; + return count; +} + +int optparse(struct optparse *options, const char *optstring) +{ + options->errmsg[0] = '\0'; + options->optopt = 0; + options->optarg = 0; + char *option = options->argv[options->optind]; + if (option == 0) { + return -1; + } else if (is_dashdash(option)) { + options->optind++; /* consume "--" */ + return -1; + } else if (!is_shortopt(option)) { + if (options->permute) { + int index = options->optind; + options->optind++; + int r = optparse(options, optstring); + permute(options, index); + options->optind--; + return r; + } else { + return -1; + } + } + option += options->subopt + 1; + options->optopt = option[0]; + int type = argtype(optstring, option[0]); + char *next = options->argv[options->optind + 1]; + switch (type) { + case -1: { + options->optind++; + char str[2] = {option[0]}; + return opterror(options, MSG_INVALID, str); + } + case OPTPARSE_NONE: + if (option[1]) { + options->subopt++; + } else { + options->subopt = 0; + options->optind++; + } + return option[0]; + case OPTPARSE_REQUIRED: + options->subopt = 0; + options->optind++; + if (option[1]) { + options->optarg = option + 1; + } else if (next != 0) { + options->optarg = next; + options->optind++; + } else { + options->optarg = 0; + char str[2] = {option[0]}; + return opterror(options, MSG_MISSING, str); + } + return option[0]; + case OPTPARSE_OPTIONAL: + options->subopt = 0; + options->optind++; + if (option[1]) + options->optarg = option + 1; + else + options->optarg = 0; + return option[0]; + } + return 0; +} + +char *optparse_arg(struct optparse *options) +{ + options->subopt = 0; + char *option = options->argv[options->optind]; + if (option != 0) + options->optind++; + return option; +} + +static inline int +longopts_end(const struct optparse_long *longopts, int i) +{ + return !longopts[i].longname && !longopts[i].shortname; +} + +static void +optstring_from_long(const struct optparse_long *longopts, char *optstring) +{ + char *p = optstring; + for (int i = 0; !longopts_end(longopts, i); i++) { + if (longopts[i].shortname) { + *p++ = longopts[i].shortname; + for (int a = 0; a < (int)longopts[i].argtype; a++) + *p++ = ':'; + } + } + *p = '\0'; +} + +/* Unlike strcmp(), handles options containing "=". */ +static int +longopts_match(const char *longname, const char *option) +{ + if (longname == 0) + return 0; + const char *a = option, *n = longname; + for (; *a && *n && *a != '='; a++, n++) + if (*a != *n) + return 0; + return *n == '\0' && (*a == '\0' || *a == '='); +} + +/* Return the part after "=", or NULL. */ +static char * +longopts_arg(char *option) +{ + for (; *option && *option != '='; option++); + if (*option == '=') + return option + 1; + else + return 0; +} + +static int +long_fallback(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ + optstring_from_long(longopts, optstring); + int result = optparse(options, optstring); + if (longindex != 0) { + *longindex = -1; + if (result != -1) + for (int i = 0; !longopts_end(longopts, i); i++) + if (longopts[i].shortname == options->optopt) + *longindex = i; + } + return result; +} + +int +optparse_long(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + char *option = options->argv[options->optind]; + if (option == 0) { + return -1; + } else if (is_dashdash(option)) { + options->optind++; /* consume "--" */ + return -1; + } else if (is_shortopt(option)) { + return long_fallback(options, longopts, longindex); + } else if (!is_longopt(option)) { + if (options->permute) { + int index = options->optind; + options->optind++; + int r = optparse_long(options, longopts, longindex); + permute(options, index); + options->optind--; + return r; + } else { + return -1; + } + } + + /* Parse as long option. */ + options->errmsg[0] = '\0'; + options->optopt = 0; + options->optarg = 0; + option += 2; /* skip "--" */ + options->optind++; + for (int i = 0; !longopts_end(longopts, i); i++) { + const char *name = longopts[i].longname; + if (longopts_match(name, option)) { + if (longindex) + *longindex = i; + options->optopt = longopts[i].shortname; + char *arg = longopts_arg(option); + if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) { + return opterror(options, MSG_TOOMANY, name); + } if (arg != 0) { + options->optarg = arg; + } else if (longopts[i].argtype == OPTPARSE_REQUIRED) { + options->optarg = options->argv[options->optind++]; + if (options->optarg == 0) + return opterror(options, MSG_MISSING, name); + } + return options->optopt; + } + } + return opterror(options, MSG_INVALID, option); +} diff --git a/src/optparse.h b/src/optparse.h new file mode 100644 index 0000000..f124b75 --- /dev/null +++ b/src/optparse.h @@ -0,0 +1,102 @@ +#ifndef OPTPARSE_H +#define OPTPARSE_H + +/** + * Optparse -- portable, reentrant, embeddable, getopt-like option parser + * + * The POSIX getopt() option parser has three fatal flaws. These flaws + * are solved by Optparse. + * + * 1) Parser state is stored entirely in global variables, some of + * which are static and inaccessible. This means only one thread can + * use getopt(). It also means it's not possible to recursively parse + * nested sub-arguments while in the middle of argument parsing. + * Optparse fixes this by storing all state on a local struct. + * + * 2) The POSIX standard provides no way to properly reset the parser. + * This means for portable code that getopt() is only good for one + * run, over one argv with one optstring. It also means subcommand + * options cannot be processed with getopt(). Most implementations + * provide a method to reset the parser, but it's not portable. + * Optparse provides an optparse_arg() function for stepping over + * subcommands and continuing parsing of options with another + * optstring. The Optparse struct itself can be passed around to + * subcommand handlers for additional subcommand option parsing. A + * full reset can be achieved by with an additional optparse_init(). + * + * 3) Error messages are printed to stderr. This can be disabled with + * opterr, but the messages themselves are still inaccessible. + * Optparse solves this by writing an error message in its errmsg + * field. The downside to Optparse is that this error message will + * always be in English rather than the current locale. + * + * Optparse should be familiar with anyone accustomed to getopt(), and + * it could be a nearly drop-in replacement. The optstring is the same + * and the fields have the same names as the getopt() global variables + * (optarg, optind, optopt). + * + * Optparse also supports GNU-style long options with optparse_long(). + * The interface is slightly different and simpler than getopt_long(). + * + * By default, argv is permuted as it is parsed, moving non-option + * arguments to the end. This can be disabled by setting the `permute` + * field to 0 after initialization. + */ + +struct optparse { + char **argv; + int permute; + int optind; + int optopt; + char *optarg; + char errmsg[64]; + int subopt; +}; + +enum optparse_argtype { OPTPARSE_NONE, OPTPARSE_REQUIRED, OPTPARSE_OPTIONAL }; + +struct optparse_long { + const char *longname; + int shortname; + enum optparse_argtype argtype; +}; + +/** + * Initializes the parser state. + */ +void optparse_init(struct optparse *options, char **argv); + +/** + * Read the next option in the argv array. + * @param optstring a getopt()-formatted option string. + * @return the next option character, -1 for done, or '?' for error + * + * Just like getopt(), a character followed by no colons means no + * argument. One colon means the option has a required argument. Two + * colons means the option takes an optional argument. + */ +int optparse(struct optparse *options, const char *optstring); + +/** + * Handles GNU-style long options in addition to getopt() options. + * This works a lot like GNU's getopt_long(). The last option in + * longopts must be all zeros, marking the end of the array. The + * longindex argument may be NULL. + */ +int +optparse_long(struct optparse *options, + const struct optparse_long *longopts, + int *longindex); + +/** + * Used for stepping over non-option arguments. + * @return the next non-option argument, or NULL for no more arguments + * + * Argument parsing can continue with optparse() after using this + * function. That would be used to parse the options for the + * subcommand returned by optparse_arg(). This function allows you to + * ignore the value of optind. + */ +char *optparse_arg(struct optparse *options); + +#endif diff --git a/src/test-c89.sh b/src/test-c89.sh deleted file mode 100755 index c90a148..0000000 --- a/src/test-c89.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh - -for f in fping.c socket4.c socket6.c seqmap.c; do - gcc -DHAVE_CONFIG_H -D_BSD_SOURCE -D_POSIX_SOURCE -I.. -Wall -std=c89 -pedantic -c -o /dev/null $f -done