Introduce ast_parse_arg() , a generic function to parse strings
authorLuigi Rizzo <rizzo@icir.org>
Tue, 17 Jul 2007 14:32:15 +0000 (14:32 +0000)
committerLuigi Rizzo <rizzo@icir.org>
Tue, 17 Jul 2007 14:32:15 +0000 (14:32 +0000)
in a consistent way. This is meant to replace the custom code
which is repeated all over the place in the various files when
parsing config files, CLI entries and other string information.

Right now the code supports parsing int32, uint32 and sockaddr_in with
optional default values and bound checks. It contains minimal error
checking, but that can be easily extended as the need arises.

Being a new API i am introducing this only in trunk, though I believe
that once the interface has been ironed out it might become a
worthwhile addition to 1.4 as well - basically, the first time
we will need to fix a piece of argument parsing code, we might as
well bring in this change and use the new API instead.

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@75379 65c4cc65-6c06-0410-ace0-fbb531ad65f3

include/asterisk/config.h
main/config.c

index e162d8c..71073f4 100644 (file)
@@ -235,6 +235,88 @@ int config_text_file_save(const char *filename, const struct ast_config *cfg, co
 
 struct ast_config *ast_config_internal_load(const char *configfile, struct ast_config *cfg, int withcomments);
 
+/*! \brief Support code to parse config file arguments
+ *
+ * The function ast_parse_arg() provides a generic interface to parse
+ * strings (e.g. numbers, network addresses and so on) in a flexible
+ * way, e.g. by doing proper error and bound checks, provide default
+ * values, and so on.
+ * The function (described later) takes a string as an argument,
+ * a set of flags to specify the result format and checks to perform,
+ * a pointer to the result, and optionally some additional arguments.
+ * It returns 0 on success, != 0 otherwise.
+ *
+ */
+enum ast_parse_flags {
+       /* low 4 bits of flags are used for the operand type */
+       PARSE_TYPE      =       0x000f,
+       /* numeric types, with optional default value and bound checks.
+        * Additional arguments are passed by value.
+        */
+       PARSE_INT16     =       0x0001,
+       PARSE_INT32     =       0x0002,
+       PARSE_UINT16    =       0x0003,
+       PARSE_UINT32    =       0x0004,
+       /* Returns a struct sockaddr_in, with optional default value
+        * (passed by reference) and port handling (accept, ignore,
+        * require, forbid). The format is 'host.name[:port]'
+        */
+       PARSE_INADDR    =       0x0005,
+
+       /* Other data types can be added as needed */
+
+       /* If PARSE_DEFAULT is set, next argument is a default value
+        * which is returned in case of error. The argument is passed
+        * by value in case of numeric types, by reference in other cases.
+        */
+       PARSE_DEFAULT   =       0x0010, /* assign default on error */
+
+       /* Request a range check, applicable to numbers. Two additional
+        * arguments are passed by value, specifying the low-high end of
+        * the range (inclusive). An error is returned if the value
+        * is outside or inside the range, respectively.
+        */
+       PARSE_IN_RANGE =        0x0020, /* accept values inside a range */
+       PARSE_OUT_RANGE =       0x0040, /* accept values outside a range */
+
+       /* Port handling, for sockaddr_in. accept/ignore/require/forbid
+        * port number after the hostname or address.
+        */
+       PARSE_PORT_MASK =       0x0300, /* 0x000: accept port if present */
+       PARSE_PORT_IGNORE =     0x0100, /* 0x100: ignore port if present */
+       PARSE_PORT_REQUIRE =    0x0200, /* 0x200: require port number */
+       PARSE_PORT_FORBID =     0x0300, /* 0x100: forbid port number */
+};
+
+/*! \brief The argument parsing routine.
+ * \param arg the string to parse. It is not modified.
+ * \param flags combination of ast_parse_flags to specify the
+ *     return type and additional checks.
+ * \param result pointer to the result. NULL is valid here, and can
+ *     be used to perform only the validity checks.
+ * \param ... extra arguments are required according to flags.
+ * \retval 0 in case of success, != 0 otherwise.
+ * \retval result returns the parsed value in case of success,
+ *     the default value in case of error, or it is left unchanged
+ *     in case of error and no default specified. Note that in certain
+ *     cases (e.g. sockaddr_in, with multi-field return values) some
+ *     of the fields in result may be changed even if an error occurs.
+ *
+ * Examples of use:
+ *     ast_parse_arg("223", PARSE_INT32|PARSE_IN_RANGE,
+ *             &a, -1000, 1000); /* returns 0, a = 223 */
+ *     ast_parse_arg("22345", PARSE_INT32|PARSE_IN_RANGE|PARSE_DEFAULT,
+ *             &a, 9999, 10, 100); /* returns 1, a = 9999 */
+ *      ast_parse_arg("22345ssf", PARSE_UINT32|PARSE_IN_RANGE, &b, 10, 100);
+ *             /* returns 1, b unchanged */
+ *      ast_parse_arg("www.foo.biz:44", PARSE_INADDR, &sa);
+ *             /* returns 0, sa contains address and port */
+ *      ast_parse_arg("www.foo.biz", PARSE_INADDR|PARSE_PORT_REQUIRE, &sa);
+ *             /* returns 1 because port is missing, sa contains address */
+ */
+int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
+        void *result, ...);
+
 #if defined(__cplusplus) || defined(c_plusplus)
 }
 #endif
index 2568dc7..e9ba97f 100644 (file)
@@ -37,6 +37,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #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,124 @@ int ast_destroy_realtime(const char *family, const char *keyfield, const char *l
        return res;
 }
 
+/*! \brief Helper function to parse arguments
+ * See documentation in config.h
+ */
+int ast_parse_arg(const char *arg, enum ast_parse_flags flags,
+        void *p_result, ...)
+{
+       va_list ap;
+       int error = 0;
+
+       va_start(ap, p_result);
+       switch (flags & PARSE_TYPE) {
+       case PARSE_INT32:
+           {
+               int32_t *result = p_result;
+               int32_t x, def = result ? *result : 0,
+                       high = (int32_t)0x7fffffff,
+                       low  = (int32_t)0x80000000;
+               /* optional argument: first default value, then range */
+               if (flags & PARSE_DEFAULT)
+                       def = va_arg(ap, int32_t);
+               if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
+                       /* range requested, update bounds */
+                       low = va_arg(ap, int32_t);
+                       high = va_arg(ap, int32_t);
+               }
+               x = strtol(arg, NULL, 0);
+               error = (x < low) || (x > high);
+               if (flags & PARSE_OUT_RANGE)
+                       error = !error;
+               if (result)
+                       *result  = error ? def : x;
+               ast_debug(3,
+                       "extract int from [%s] in [%d, %d] gives [%d](%d)\n",
+                       arg, low, high,
+                       result ? *result : x, error);
+               break;
+           }
+
+       case PARSE_UINT32:
+           {
+               uint32_t *result = p_result;
+               uint32_t x, def = result ? *result : 0,
+                       low = 0, high = (uint32_t)~0;
+               /* optional argument: first default value, then range */
+               if (flags & PARSE_DEFAULT)
+                       def = va_arg(ap, uint32_t);
+               if (flags & (PARSE_IN_RANGE|PARSE_OUT_RANGE)) {
+                       /* range requested, update bounds */
+                       low = va_arg(ap, uint32_t);
+                       high = va_arg(ap, uint32_t);
+               }
+               x = strtoul(arg, NULL, 0);
+               error = (x < low) || (x > high);
+               if (flags & PARSE_OUT_RANGE)
+                       error = !error;
+               if (result)
+                       *result  = error ? def : x;
+               ast_debug(3,
+                       "extract uint from [%s] in [%u, %u] gives [%u](%d)\n",
+                       arg, low, high,
+                       result ? *result : x, error);
+               break;
+           }
+
+       case PARSE_INADDR:
+           {
+               char *port, *buf;
+               struct sockaddr_in _sa_buf;     /* buffer for the result */
+               struct sockaddr_in *sa = p_result ?
+                       (struct sockaddr_in *)p_result : &_sa_buf;
+               /* default is either the supplied value or the result itself */
+               struct sockaddr_in *def = (flags & PARSE_DEFAULT) ?
+                       va_arg(ap, struct sockaddr_in *) : sa;
+               struct hostent *hp;
+               struct ast_hostent ahp;
+
+               bzero(&_sa_buf, sizeof(_sa_buf)); /* clear buffer */
+               /* duplicate the string to strip away the :port */
+               port = ast_strdupa(arg);
+               buf = strsep(&port, ":");
+               sa->sin_family = AF_INET;       /* assign family */
+               /*
+                * honor the ports flag setting, assign default value
+                * in case of errors or field unset.
+                */
+               flags &= PARSE_PORT_MASK; /* the only flags left to process */
+               if (port) {
+                       if (flags == PARSE_PORT_FORBID) {
+                               error = 1;      /* port was forbidden */
+                               sa->sin_port = def->sin_port;
+                       } else if (flags == PARSE_PORT_IGNORE)
+                               sa->sin_port = def->sin_port;
+                       else /* accept or require */
+                               sa->sin_port = htons(strtol(port, NULL, 0));
+               } else {
+                       sa->sin_port = def->sin_port;
+                       if (flags == PARSE_PORT_REQUIRE)
+                               error = 1;
+               }
+               /* Now deal with host part, even if we have errors before. */
+               hp = ast_gethostbyname(buf, &ahp);
+               if (hp) /* resolved successfully */
+                       memcpy(&sa->sin_addr, hp->h_addr, sizeof(sa->sin_addr));
+               else {
+                       error = 1;
+                       sa->sin_addr = def->sin_addr;
+               }
+               ast_debug(3,
+                       "extract inaddr from [%s] gives [%s:%d](%d)\n",
+                       arg, ast_inet_ntoa(sa->sin_addr),
+                       ntohs(sa->sin_port), error);
+               break;
+           }
+       }
+       va_end(ap);
+       return error;
+}
+
 static int config_command(int fd, int argc, char **argv) 
 {
        struct ast_config_engine *eng;