* (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
AC_PROG_CC
AM_PROG_CC_C_O
+AC_PROG_CC_STDC
AC_PROG_CPP
AC_PROG_INSTALL
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
#include "fping.h"
#include "options.h"
-
-/*** autoconf includes ***/
+#include "optparse.h"
#include <errno.h>
#include <signal.h>
#include <ctype.h>
#include <netdb.h>
-#include <getopt.h>
-
#include <sys/select.h>
/*** externals ***/
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
perror("cannot setuid");
}
+ prog = argv[0];
+ optparse_init(&optparse_state, argv);
ident = getpid() & 0xFFFF;
verbose_flag = 1;
backoff_flag = 1;
/* 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) {
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;
break;
case 'b':
- if (!sscanf(optarg, "%u", &ping_data_size))
+ if (!sscanf(optparse_state.optarg, "%u", &ping_data_size))
usage(1);
break;
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;
break;
case 'B':
- if (!(backoff = atof(optarg)))
+ if (!(backoff = atof(optparse_state.optarg)))
usage(1);
break;
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':
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;
}
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
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");
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;
}
/* 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 */
--- /dev/null
+#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);
+}
--- /dev/null
+#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
+++ /dev/null
-#!/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