pull/89/merge
David Schweikert 8 years ago
parent 7605ef1b90
commit e5c3839073

@ -18,6 +18,9 @@ Unreleased
* (feature) --enable-ipv6 is now default * (feature) --enable-ipv6 is now default
* (feature) New option '-4' to force IPv4 * (feature) New option '-4' to force IPv4
* (feature) New option '-6' to force IPv6 * (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> 2017-02-09 David Schweikert <david@schweikert.ch>
* Version 3.16 * Version 3.16

@ -52,6 +52,7 @@ dnl Checks for programs.
AC_PROG_CC AC_PROG_CC
AM_PROG_CC_C_O AM_PROG_CC_C_O
AC_PROG_CC_STDC
AC_PROG_CPP AC_PROG_CPP
AC_PROG_INSTALL AC_PROG_INSTALL

@ -2,7 +2,7 @@ AM_CFLAGS = -Wall -Wextra -Wno-sign-compare
sbin_PROGRAMS = fping 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 fping_DEPENDENCIES = ../config.h
if IPV6 if IPV6

@ -36,8 +36,7 @@ extern "C" {
#include "fping.h" #include "fping.h"
#include "options.h" #include "options.h"
#include "optparse.h"
/*** autoconf includes ***/
#include <errno.h> #include <errno.h>
#include <signal.h> #include <signal.h>
@ -79,8 +78,6 @@ extern "C" {
#include <ctype.h> #include <ctype.h>
#include <netdb.h> #include <netdb.h>
#include <getopt.h>
#include <sys/select.h> #include <sys/select.h>
/*** externals ***/ /*** externals ***/
@ -348,8 +345,7 @@ int main(int argc, char** argv)
uid_t uid; uid_t uid;
int tos = 0; int tos = 0;
HOST_ENTRY* cursor; HOST_ENTRY* cursor;
struct optparse optparse_state;
prog = argv[0];
socket4 = open_ping_socket_ipv4(ping_data_size); socket4 = open_ping_socket_ipv4(ping_data_size);
#ifdef IPV6 #ifdef IPV6
@ -362,6 +358,8 @@ int main(int argc, char** argv)
perror("cannot setuid"); perror("cannot setuid");
} }
prog = argv[0];
optparse_init(&optparse_state, argv);
ident = getpid() & 0xFFFF; ident = getpid() & 0xFFFF;
verbose_flag = 1; verbose_flag = 1;
backoff_flag = 1; backoff_flag = 1;
@ -369,7 +367,7 @@ int main(int argc, char** argv)
/* get command line options */ /* 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) { switch (c) {
case '4': case '4':
if (hints_ai_family != AF_UNSPEC) { if (hints_ai_family != AF_UNSPEC) {
@ -413,37 +411,37 @@ int main(int argc, char** argv)
break; break;
case 't': case 't':
if (!(timeout = (unsigned int)atoi(optarg) * 100)) if (!(timeout = (unsigned int)atoi(optparse_state.optarg) * 100))
usage(1); usage(1);
break; break;
case 'r': case 'r':
if (!sscanf(optarg, "%u", &retry)) if (!sscanf(optparse_state.optarg, "%u", &retry))
usage(1); usage(1);
break; break;
case 'i': case 'i':
if (!sscanf(optarg, "%u", &interval)) if (!sscanf(optparse_state.optarg, "%u", &interval))
usage(1); usage(1);
interval *= 100; interval *= 100;
break; break;
case 'p': case 'p':
if (!(perhost_interval = (unsigned int)atoi(optarg) * 100)) if (!(perhost_interval = (unsigned int)atoi(optparse_state.optarg) * 100))
usage(1); usage(1);
break; break;
case 'c': case 'c':
if (!(count = (unsigned int)atoi(optarg))) if (!(count = (unsigned int)atoi(optparse_state.optarg)))
usage(1); usage(1);
count_flag = 1; count_flag = 1;
break; break;
case 'C': case 'C':
if (!(count = (unsigned int)atoi(optarg))) if (!(count = (unsigned int)atoi(optparse_state.optarg)))
usage(1); usage(1);
count_flag = 1; count_flag = 1;
@ -451,7 +449,7 @@ int main(int argc, char** argv)
break; break;
case 'b': case 'b':
if (!sscanf(optarg, "%u", &ping_data_size)) if (!sscanf(optparse_state.optarg, "%u", &ping_data_size))
usage(1); usage(1);
break; break;
@ -468,7 +466,7 @@ int main(int argc, char** argv)
case 'Q': case 'Q':
verbose_flag = 0; verbose_flag = 0;
quiet_flag = 1; quiet_flag = 1;
if (!(report_interval = (unsigned int)atoi(optarg) * 100000)) if (!(report_interval = (unsigned int)atoi(optparse_state.optarg) * 100000))
usage(1); usage(1);
break; break;
@ -495,7 +493,7 @@ int main(int argc, char** argv)
break; break;
case 'B': case 'B':
if (!(backoff = atof(optarg))) if (!(backoff = atof(optparse_state.optarg)))
usage(1); usage(1);
break; break;
@ -526,25 +524,25 @@ int main(int argc, char** argv)
break; break;
case 'H': case 'H':
if (!(ttl = (u_int)atoi(optarg))) if (!(ttl = (u_int)atoi(optparse_state.optarg)))
usage(1); usage(1);
break; break;
#if defined(DEBUG) || defined(_DEBUG) #if defined(DEBUG) || defined(_DEBUG)
case 'z': case 'z':
if (!(debugging = (unsigned int)atoi(optarg))) if (!(debugging = (unsigned int)atoi(optparse_state.optarg)))
usage(1); usage(1);
break; break;
#endif /* DEBUG || _DEBUG */ #endif /* DEBUG || _DEBUG */
case 'v': case 'v':
printf("%s: Version %s\n", argv[0], VERSION); printf("%s: Version %s\n", prog, VERSION);
printf("%s: comments to %s\n", argv[0], EMAIL); printf("%s: comments to %s\n", prog, EMAIL);
exit(0); exit(0);
case 'f': case 'f':
filename = optarg; filename = optparse_state.optarg;
break; break;
case 'g': case 'g':
@ -554,12 +552,12 @@ int main(int argc, char** argv)
break; break;
case 'S': case 'S':
if (inet_pton(AF_INET, optarg, &src_addr)) { if (inet_pton(AF_INET, optparse_state.optarg, &src_addr)) {
src_addr_set = 1; src_addr_set = 1;
break; break;
} }
#ifdef IPV6 #ifdef IPV6
if (inet_pton(AF_INET6, optarg, &src_addr6)) { if (inet_pton(AF_INET6, optparse_state.optarg, &src_addr6)) {
src_addr6_set = 1; src_addr6_set = 1;
break; break;
} }
@ -570,19 +568,19 @@ int main(int argc, char** argv)
case 'I': case 'I':
#ifdef SO_BINDTODEVICE #ifdef SO_BINDTODEVICE
if (socket4) { 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)"); perror("binding to specific interface (SO_BINTODEVICE)");
} }
} }
#ifdef IPV6 #ifdef IPV6
if (socket6) { 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"); perror("binding to specific interface (SO_BINTODEVICE), IPV6");
} }
} }
#endif #endif
#else #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); exit(3);
; ;
#endif #endif
@ -593,7 +591,7 @@ int main(int argc, char** argv)
break; break;
case 'O': case 'O':
if (sscanf(optarg, "%i", &tos)) { if (sscanf(optparse_state.optarg, "%i", &tos)) {
if (socket4) { if (socket4) {
if (setsockopt(socket4, IPPROTO_IP, IP_TOS, &tos, sizeof(tos))) { if (setsockopt(socket4, IPPROTO_IP, IP_TOS, &tos, sizeof(tos))) {
perror("setting type of service octet IP_TOS"); perror("setting type of service octet IP_TOS");
@ -615,16 +613,16 @@ int main(int argc, char** argv)
outage_flag = 1; outage_flag = 1;
break; break;
default: case '?':
fprintf(stderr, "%s: %s\n", argv[0], optparse_state.errmsg);
fprintf(stderr, "see 'fping -h' for usage information\n"); fprintf(stderr, "see 'fping -h' for usage information\n");
exit(1); exit(1);
break; break;
} }
} }
/* if we are called 'fping6', assume '-6' */ /* if we are called 'fping6', assume '-6' */
if (strstr(argv[0], "fping6")) { if (strstr(prog, "fping6")) {
hints_ai_family = AF_INET6; 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 */ /* handle host names supplied on command line or in a file */
/* if the generate_flag is on, then generate the IP list */ /* if the generate_flag is on, then generate the IP list */
argv = &argv[optind]; argv = &argv[optparse_state.optind];
argc -= optind; argc -= optparse_state.optind;
/* cover allowable conditions */ /* cover allowable conditions */

@ -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);
}

@ -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

@ -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
Loading…
Cancel
Save