]> git.gsnw.org Git - fping.git/commitdiff
integrate optparse (https://github.com/skeeto/optparse)
authorDavid Schweikert <david@schweikert.ch>
Thu, 9 Feb 2017 11:19:27 +0000 (12:19 +0100)
committerDavid Schweikert <david@schweikert.ch>
Thu, 9 Feb 2017 11:19:27 +0000 (12:19 +0100)
ChangeLog
configure.ac
src/Makefile.am
src/fping.c
src/optparse.c [new file with mode: 0644]
src/optparse.h [new file with mode: 0644]
src/test-c89.sh [deleted file]

index aa657acd6077e3846a10549306596d4806c41c68..efa9fe0419c806fe4890fcffac8bb48ebc63197b 100644 (file)
--- 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  <david@schweikert.ch>
   * Version 3.16
index 94a6db73abe7ce6f8721d7f5c1a85d5a035cb11b..8b0c4ffdbe2d3079fd6e651f1a1d804a8b1f6f29 100644 (file)
@@ -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
 
index c518d34d4d153555c9eac20ba5a2f17691114be7..c58e47481f524aa12953ed641354efb7e3b78db7 100644 (file)
@@ -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
index f00a13eaae7d740fe5143b5d270cb8c85b04935a..26b6ea05958f92b463c1c7bf3c35b58e88cb75df 100644 (file)
@@ -36,8 +36,7 @@ extern "C" {
 
 #include "fping.h"
 #include "options.h"
-
-/*** autoconf includes ***/
+#include "optparse.h"
 
 #include <errno.h>
 #include <signal.h>
@@ -79,8 +78,6 @@ extern "C" {
 #include <ctype.h>
 #include <netdb.h>
 
-#include <getopt.h>
-
 #include <sys/select.h>
 
 /*** 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 (file)
index 0000000..4242bff
--- /dev/null
@@ -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 (file)
index 0000000..f124b75
--- /dev/null
@@ -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 (executable)
index c90a148..0000000
+++ /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