[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