[asterisk-dev] RFC: generic argument parsing routines
Luigi Rizzo
rizzo at icir.org
Mon Jul 16 11:10:51 CDT 2007
Below is patch that could be used as a proof of concept for the
generic argument parsing routines that i have desired to write for
a long time. There are a ton of places in the source where such
code is replicated, and sometimes in inconsistent ways. So i think
having an API like this would make the configuration code a lot
more robust and cleaner.
At the moment my code only implements integer types and sockaddr_in
(with optional port types), provides the ability to specify a
default value (to be used in case of error/missing parameters)
and for integer types, provides in_range or outside_range checks.
The function returns 0 on success, 1 on error, and would be called
with something like this:
err = ast_parsearg("1238", PARSE_INT32|PARSE_IN_RANGE|PARSE_DEFAULT, &x, (void *)99, (void *)100, (void *)200);
/* returns err = 1, x = 99 because it is outside of range */
err = ast_parsearg("123", PARSE_INT32 | PARSE_IN_RANGE, &x, (void *)99, (void *)100, (void *)200);
/* returns err = 0, x = 123 */
err = ast_parsearg("www.unipi.itt:3366", PARSE_INADDR, &sa, NULL, 0, 0);
/* returns 0.0.0.0:3366, err = 1 because hostname does not resolve */
Clearly, the API needs to be improved a little bit - e.g. the (void *)
casts must be removed by implementing the function with varargs.
The flags PARSE_... are a bit too verbose now, but i am
concerned that a 'format string' with pieces like the
ones below might result too cryptic:
d int16_t
ld int32_t
u uint16_t
lu uint32_t
s string
*s string, malloc a buffer for the result
A sockaddr_in
<> in-range
>< outside-range
= default value
p require port value in sockaddr_in
!p forbid port value in sockaddr_in
-p ignore port value in sockaddr_in
RE use a regexp to match the result
What do you think ?
cheers
luigi
Index: include/asterisk/config.h
===================================================================
--- include/asterisk/config.h (revision 75181)
+++ include/asterisk/config.h (working copy)
@@ -235,6 +235,48 @@
struct ast_config *ast_config_internal_load(const char *configfile, struct ast_config *cfg, int withcomments);
+/*! \brief Support routines for parsing config file arguments
+ *
+ * Entries in config files may contain strings, numbers (integer or
+ * floating point), addresses and so on. The following routines and
+ * enums help parsing these data in a consistent way.
+ *
+ * The general interface is ast_parsearg(), with a 'format' argument
+ * indicating the type of the return value, and possibly extra parameters
+ * e.g. for bound checks add so on.
+ *
+ */
+enum ast_parse_flags {
+ /* low 4 bits are used for the operand type */
+ PARSE_TYPE = 0x000f,
+ /* numeric types have optional bounds check and default value */
+ PARSE_INT16 = 0x0001,
+ PARSE_INT32 = 0x0002,
+ PARSE_UINT16 = 0x0003,
+ PARSE_UINT32 = 0x0004,
+ PARSE_DOUBLE = 0x0005,
+ /* strings have optional default, malloc and regexp */
+ PARSE_STRING = 0x0006,
+ /* inaddr have optional default and port processing */
+ PARSE_INADDR = 0x0007,
+
+ PARSE_DEFAULT = 0x0010, /* assign default on error */
+ /* range checks, applicable to numbers */
+ PARSE_IN_RANGE = 0x0020, /* accept values inside a range */
+ PARSE_OUT_RANGE = 0x0040, /* accept values outside a range */
+ /* ignore/accept/require/forbid port numbers in sockaddr_in */
+ PARSE_PORT_MASK = 0x0300, /* 0x000: accept if present */
+ PARSE_PORT_IGNORE = 0x0100, /* 0x100: ignore if present */
+ PARSE_PORT_REQUIRE = 0x0200, /* 0x200: mandatory */
+ PARSE_PORT_FORBID = 0x0300, /* 0x100: forbidden */
+
+ PARSE_STRING_MALLOC = 0x0400, /* malloc buffer for strings */
+ PARSE_STRING_REGEXP = 0x0800, /* use regexp in string parsing */
+};
+
+int ast_parsearg(const char *arg, enum ast_parse_flags flags,
+ void *result, void *p_default, void *p_low, void *p_high);
+
#if defined(__cplusplus) || defined(c_plusplus)
}
#endif
Index: main/config.c
===================================================================
--- main/config.c (revision 75181)
+++ main/config.c (working copy)
@@ -37,6 +37,7 @@
#include <errno.h>
#include <time.h>
#include <sys/stat.h>
+#include <sys/socket.h> /* for AF_INET */
#define AST_INCLUDE_GLOB 1
#ifdef AST_INCLUDE_GLOB
#if defined(__Darwin__) || defined(__CYGWIN__)
@@ -1444,6 +1445,105 @@
return res;
}
+/*! \brief helper function to parse arguments
+ * In the flags one specifies the type, options (e.g.
+ * assign defaults, match within a range or outside it, etc.)
+ */
+int ast_parsearg(const char *arg, enum ast_parse_flags flags,
+ void *result, void *p_default, void *p_low, void *p_high)
+{
+ int error = 0;
+
+ switch (flags & PARSE_TYPE) {
+ case PARSE_INT32: /* only affects range. type is still int32_t */
+ case PARSE_INT16:
+ {
+ int32_t x, low = -32768, high = 32767;
+ if ((flags & PARSE_TYPE) == PARSE_INT32) {
+ high = (int32_t)0x7fffffff;
+ low = (int32_t)~high;
+ }
+ x = strtol(arg, NULL, 0);
+ if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
+ low = (int32_t)p_low;
+ high = (int32_t)p_high;
+ }
+ error = (x < low) || (x > high);
+ if (flags & PARSE_OUT_RANGE)
+ error = !error;
+ if (error) /* either default or preserve original */
+ x = (flags & PARSE_DEFAULT) ? (int32_t)p_default : *((int32_t *)result);
+ *((int32_t *)result) = x;
+ ast_log(LOG_WARNING, "extract int from [%s] in range [%d, %d] gives [%d](%d)\n",
+ arg, low, high, x, error);
+ break;
+ }
+
+ case PARSE_UINT32:
+ case PARSE_UINT16:
+ {
+ uint32_t x, low = 0, high = 0xffff;
+ x = strtol(arg, NULL, 0);
+ if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
+ low = (uint32_t)p_low;
+ high = (uint32_t)p_high;
+ }
+ error = (x < low) || (x > high);
+ if (flags & PARSE_OUT_RANGE)
+ error = !error;
+ if (error) /* either default or preserve original */
+ x = (flags & PARSE_DEFAULT) ? (uint32_t)p_default : *((uint32_t *)result);
+ *((uint32_t *)result) = x;
+ ast_log(LOG_WARNING, "extract uint from [%s] in range [%u, %u] gives [%u](%d)\n",
+ arg, low, high, x, error);
+ break;
+ }
+
+ case PARSE_INADDR:
+ {
+ char *port, *buf;
+ struct sockaddr_in *sa = (struct sockaddr_in *)result;
+ struct sockaddr_in *sa_default = NULL;
+
+ if (flags & PARSE_DEFAULT) /* assign default on error */
+ sa_default = (struct sockaddr_in *)p_default;
+ flags &= PARSE_PORT_MASK; /* the only flags left to process */
+ port = ast_strdupa(arg);
+ buf = strsep(&port, ":");
+ bzero(sa, sizeof(*sa));
+ sa->sin_family = AF_INET;
+ if (port) {
+ if (flags == PARSE_PORT_FORBID) /* forbidden, report an error */
+ error = 1;
+ else if (flags != PARSE_PORT_IGNORE)
+ sa->sin_port = htons(strtol(port, NULL, 0));
+ } else {
+ if (flags == PARSE_PORT_REQUIRE)
+ error = 1;
+ }
+ if (error) {
+ if (sa_default)
+ sa->sin_port = sa_default->sin_port;
+ } else {
+ struct hostent *hp;
+ struct ast_hostent ahp;
+ hp = ast_gethostbyname(buf, &ahp);
+ if (hp)
+ memcpy(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr));
+ else {
+ error = 1;
+ if (sa_default)
+ sa->sin_addr = sa_default->sin_addr;
+ }
+ }
+ ast_log(LOG_WARNING, "extract inaddr from [%s] gives [%s:%d](%d)\n",
+ arg, ast_inet_ntoa(sa->sin_addr), ntohs(sa->sin_port), error);
+ }
+ break;
+ }
+ return error;
+}
+
static int config_command(int fd, int argc, char **argv)
{
struct ast_config_engine *eng;
More information about the asterisk-dev
mailing list