diff --git a/Makefile b/Makefile index eaeea21eb..c832338d0 100644 --- a/Makefile +++ b/Makefile @@ -511,7 +511,7 @@ OBJS = src/haproxy.o src/sessionhash.o src/base64.o src/protocols.o \ src/uri_auth.o src/standard.o src/buffers.o src/log.o src/task.o \ src/time.o src/fd.o src/pipe.o src/regex.o src/cfgparse.o src/server.o \ src/checks.o src/queue.o src/frontend.o src/proxy.o src/peers.o \ - src/stick_table.o src/proto_uxst.o \ + src/arg.o src/stick_table.o src/proto_uxst.o \ src/proto_http.o src/stream_sock.o src/appsession.o src/backend.o \ src/lb_chash.o src/lb_fwlc.o src/lb_fwrr.o src/lb_map.o src/lb_fas.o \ src/stream_interface.o src/dumpstats.o src/proto_tcp.o \ diff --git a/Makefile.bsd b/Makefile.bsd index ef7c57784..14dafacca 100644 --- a/Makefile.bsd +++ b/Makefile.bsd @@ -114,7 +114,7 @@ OBJS = src/haproxy.o src/sessionhash.o src/base64.o src/protocols.o \ src/session.o src/hdr_idx.o src/ev_select.o src/signal.o \ src/lb_chash.o src/lb_fwlc.o src/lb_fwrr.o src/lb_map.o src/lb_fas.o \ src/ev_poll.o src/ev_kqueue.o \ - src/acl.o src/memory.o src/freq_ctr.o \ + src/arg.o src/acl.o src/memory.o src/freq_ctr.o \ src/auth.o src/stick_table.o src/pattern.o EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o \ diff --git a/Makefile.osx b/Makefile.osx index ec6f246e4..a4389ef7c 100644 --- a/Makefile.osx +++ b/Makefile.osx @@ -111,7 +111,7 @@ OBJS = src/haproxy.o src/sessionhash.o src/base64.o src/protocols.o \ src/session.o src/hdr_idx.o src/ev_select.o src/signal.o \ src/lb_chash.o src/lb_fwlc.o src/lb_fwrr.o src/lb_map.o src/lb_fas.o \ src/ev_poll.o \ - src/acl.o src/memory.o src/freq_ctr.o \ + src/arg.o src/acl.o src/memory.o src/freq_ctr.o \ src/auth.o src/stick_table.o src/pattern.o EBTREE_OBJS = $(EBTREE_DIR)/ebtree.o \ diff --git a/include/proto/arg.h b/include/proto/arg.h new file mode 100644 index 000000000..68eb379db --- /dev/null +++ b/include/proto/arg.h @@ -0,0 +1,67 @@ +/* + * include/proto/arg.h + * This file contains functions and macros declarations for generic argument parsing. + * + * Copyright 2012 Willy Tarreau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _PROTO_ARG_H +#define _PROTO_ARG_H + +#include + +/* Some macros used to build some arg list. We can declare various argument + * combinations from 0 to 7 args using a single 32-bit integer. The first + * argument of these macros is always the mandatory number of arguments, and + * remaining ones are optional args. Note: ARGM() may also be used to return + * the number of mandatory arguments in a mask. + */ +#define ARGM(m) \ + (m & 15) + +#define ARG1(m, t1) \ + (ARGM(m) + (ARGT_##t1 << 4)) + +#define ARG2(m, t1, t2) \ + (ARG1(m, t1) + (ARGT_##t2 << 8)) + +#define ARG3(m, t1, t2, t3) \ + (ARG2(m, t1, t2) + (ARGT_##t3 << 12)) + +#define ARG4(m, t1, t2, t3, t4) \ + (ARG3(m, t1, t2, t3) + (ARGT_##t4 << 16)) + +#define ARG5(m, t1, t2, t3, t4, t5) \ + (ARG4(m, t1, t2, t3, t4) + (ARGT_##t5 << 20)) + +#define ARG6(m, t1, t2, t3, t4, t5, t6) \ + (ARG5(m, t1, t2, t3, t4, t5) + (ARGT_##t6 << 24)) + +#define ARG7(m, t1, t2, t3, t4, t5, t6, t7) \ + (ARG6(m, t1, t2, t3, t4, t5, t6) + (ARGT_##t7 << 28)) + +int make_arg_list(const char *in, int len, unsigned int mask, struct arg **argp, + char **err_msg, const char **err_ptr, int *err_arg); + +#endif /* _PROTO_ARG_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/include/types/arg.h b/include/types/arg.h new file mode 100644 index 000000000..236972148 --- /dev/null +++ b/include/types/arg.h @@ -0,0 +1,78 @@ +/* + * include/types/arg.h + * This file contains structure declarations for generaic argument parsing. + * + * Copyright 2012 Willy Tarreau + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, version 2.1 + * exclusively. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _TYPES_ARG_H +#define _TYPES_ARG_H + +#include +#include +#include + +enum { + ARGT_STOP = 0, /* end of the arg list */ + ARGT_UINT, /* unsigned integer, which is a positive integer without any sign */ + ARGT_SINT, /* signed integer, the sign (+/-) was explicit. Falls back to UINT if no sign. */ + ARGT_STR, /* string */ + ARGT_IPV4, /* an IPv4 address */ + ARGT_MSK4, /* an IPv4 address mask (integer or dotted), stored as ARGT_IPV4 */ + ARGT_IPV6, /* an IPv6 address */ + ARGT_MSK6, /* an IPv6 address mask (integer or dotted), stored as ARGT_IPV4 */ + ARGT_TIME, /* a delay in ms by default, stored as ARGT_UINT */ + ARGT_SIZE, /* a size in bytes by default, stored as ARGT_UINT */ + ARGT_FE, /* a pointer to a frontend only */ + ARGT_BE, /* a pointer to a backend only */ + ARGT_TAB, /* a pointer to a stick table */ + ARGT_SRV, /* a pointer to a server */ + ARGT_USR, /* a pointer to a user list */ + ARGT_UNASSIGNED15, /* will probably be used for variables later */ + ARGT_NBTYPES /* no more values past 15 */ +}; + +/* some types that are externally defined */ +struct proxy; +struct server; +struct userlist; + +union arg_data { + unsigned int uint; /* used for uint, time, size */ + int sint; + struct chunk str; + struct in_addr ipv4; + struct in6_addr ipv6; + struct proxy *prx; /* used for fe, be, tables */ + struct server *srv; + struct userlist *usr; +}; + +struct arg { + int type; /* argument type */ + union arg_data data; /* argument data */ +}; + + +#endif /* _TYPES_ARG_H */ + +/* + * Local variables: + * c-indent-level: 8 + * c-basic-offset: 8 + * End: + */ diff --git a/src/arg.c b/src/arg.c new file mode 100644 index 000000000..0e11c498d --- /dev/null +++ b/src/arg.c @@ -0,0 +1,251 @@ +/* + * Functions used to parse typed argument lists + * + * Copyright 2012 Willy Tarreau + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + * + */ + +#include +#include +#include + +#include +#include + +static const char *arg_type_names[ARGT_NBTYPES] = { + [ARGT_STOP] = "end of arguments", + [ARGT_UINT] = "unsigned integer", + [ARGT_SINT] = "signed integer", + [ARGT_STR] = "string", + [ARGT_IPV4] = "IPv4 address", + [ARGT_MSK4] = "IPv4 mask", + [ARGT_IPV6] = "IPv6 address", + [ARGT_MSK6] = "IPv6 mask", + [ARGT_TIME] = "delay", + [ARGT_SIZE] = "size", + [ARGT_FE] = "frontend", + [ARGT_BE] = "backend", + [ARGT_TAB] = "table", + [ARGT_SRV] = "server", + [ARGT_USR] = "user list", + /* Unassigned types must never happen. Better crash during parsing if they do. */ +}; + +/* This function builds an argument list from a config line. It returns the + * number of arguments found, or <0 in case of any error. Everything needed + * it automatically allocated. A pointer to an error message might be returned + * in err_msg if not NULL, in which case it would be allocated and the caller + * will have to check it and free it. The output arg list is returned in argp + * which must be valid. The returned array is always terminated by an arg of + * type ARGT_STOP (0), unless the mask indicates that no argument is supported. + * The mask is composed of a number of mandatory arguments in its lower 4 bits, + * and a concatenation of each argument type in each subsequent 4-bit block. If + * is not NULL, it must point to a freeable or NULL pointer. + */ +int make_arg_list(const char *in, int len, unsigned int mask, struct arg **argp, + char **err_msg, const char **err_ptr, int *err_arg) +{ + int nbarg; + int pos; + struct arg *arg, *arg_list = NULL; + const char *beg; + char *word = NULL; + const char *ptr_err = NULL; + int min_arg; + + min_arg = mask & 15; + mask >>= 4; + + pos = 0; + /* find between 0 and 8 the max number of args supported by the mask */ + for (nbarg = 0; nbarg < 8 && ((mask >> (nbarg * 4)) & 0xF); nbarg++); + + if (!nbarg) + goto end_parse; + + /* Note: an empty input string contains an empty argument if this argument + * is marked mandatory. Otherwise we can ignore it. + */ + if (!len && !min_arg) + goto end_parse; + + arg = arg_list = calloc(nbarg + 1, sizeof(*arg)); + + /* Note: empty arguments after a comma always exist. */ + while (pos < nbarg) { + beg = in; + while (len && *in != ',') { + in++; + len--; + } + + /* we have a new argument between and (not included). + * For ease of handling, we copy it into a zero-terminated word. + * By default, the output argument will be the same type of the + * expected one. + */ + free(word); + word = my_strndup(beg, in - beg); + + arg->type = (mask >> (pos * 4)) & 15; + + switch (arg->type) { + case ARGT_SINT: + if (in == beg) // empty number + goto parse_err; + else if (*beg < '0' || *beg > '9') { + arg->data.sint = strl2uic(beg + 1, in - beg - 1); + if (*beg == '-') + arg->data.sint = -arg->data.sint; + else if (*beg != '+') // invalid first character + goto parse_err; + break; + } + + arg->type = ARGT_UINT; + /* fall through ARGT_UINT if no sign is present */ + + case ARGT_UINT: + if (in == beg) // empty number + goto parse_err; + + arg->data.uint = strl2uic(beg, in - beg); + break; + + case ARGT_FE: + case ARGT_BE: + case ARGT_TAB: + case ARGT_SRV: + case ARGT_USR: + case ARGT_STR: + /* all types that must be resolved are stored as strings + * during the parsing. The caller must at one point resolve + * them and free the string. + */ + arg->data.str.str = word; + arg->data.str.len = in - beg; + arg->data.str.size = arg->data.str.len + 1; + word = NULL; + break; + + case ARGT_IPV4: + if (in == beg) // empty address + goto parse_err; + + if (inet_pton(AF_INET, word, &arg->data.ipv4) <= 0) + goto parse_err; + break; + + case ARGT_MSK4: + if (in == beg) // empty mask + goto parse_err; + + if (!str2mask(word, &arg->data.ipv4)) + goto parse_err; + + arg->type = ARGT_IPV4; + break; + + case ARGT_IPV6: + if (in == beg) // empty address + goto parse_err; + + if (inet_pton(AF_INET6, word, &arg->data.ipv6) <= 0) + goto parse_err; + break; + + case ARGT_MSK6: /* not yet implemented */ + goto parse_err; + + case ARGT_TIME: + if (in == beg) // empty time + goto parse_err; + + ptr_err = parse_time_err(word, &arg->data.uint, TIME_UNIT_MS); + if (ptr_err) + goto parse_err; + + arg->type = ARGT_UINT; + break; + + case ARGT_SIZE: + if (in == beg) // empty size + goto parse_err; + + ptr_err = parse_size_err(word, &arg->data.uint); + if (ptr_err) + goto parse_err; + + arg->type = ARGT_UINT; + break; + + /* FIXME: other types need to be implemented here */ + default: + goto parse_err; + } + + pos++; + arg++; + + /* don't go back to parsing if we reached end */ + if (!len || pos >= nbarg) + break; + + /* skip comma */ + in++; len--; + } + + end_parse: + free(word); word = NULL; + + if (pos < min_arg) { + /* not enough arguments */ + if (err_msg) + memprintf(err_msg, + "Missing arguments (got %d/%d), type '%s' expected", + pos, min_arg, arg_type_names[(mask >> (pos * 4)) & 15]); + goto err; + } + + if (len) { + /* too many arguments, starting at */ + if (err_msg) { + /* the caller is responsible for freeing this message */ + word = my_strndup(in, len); + memprintf(err_msg, "End of arguments expected at '%s'", word); + free(word); word = NULL; + } + goto err; + } + + /* note that pos might be < nbarg and this is not an error, it's up to the + * caller to decide what to do with optional args. + */ + *argp = arg_list; + + if (err_arg) + *err_arg = pos; + if (err_ptr) + *err_ptr = in; + return pos; + + parse_err: + if (err_msg) { + memprintf(err_msg, "Failed to parse '%s' as type '%s'", + word, arg_type_names[(mask >> (pos * 4)) & 15]); + } + + err: + free(word); + free(arg_list); + if (err_arg) + *err_arg = pos; + if (err_ptr) + *err_ptr = in; + return -1; +} diff --git a/tests/test-arg.c b/tests/test-arg.c new file mode 100644 index 000000000..222f2b234 --- /dev/null +++ b/tests/test-arg.c @@ -0,0 +1,44 @@ +#include +#include +#include +#include "proto/arg.h" + +int main(int argc, char **argv) +{ + int nbargs, err_arg, mask; + struct arg *argp; + char *err_msg = NULL; + const char *err_ptr = NULL; + + if (argc < 2) { + printf("Usage: %s arg_list [arg_mask]\n" + " mask defaults to 0x86543290\n" + " eg: %s 10k,+20,Host,1.2.3.4,24,::5.6.7.8,120s\n", *argv, *argv); + return 1; + } + + mask = ARG7(0,SIZE,SINT,STR,IPV4,MSK4,IPV6,TIME); + if (argc >= 3) + mask = atoll(argv[2]); + + printf("Using mask=0x%08x\n", mask); + nbargs = make_arg_list(argv[1], strlen(argv[1]), mask, + &argp, &err_msg, &err_ptr, &err_arg); + + printf("nbargs=%d\n", nbargs); + if (nbargs < 0) { + printf("err_msg=%s\n", err_msg); free(err_msg); + printf("err_ptr=%s (str+%d)\n", err_ptr, err_ptr - argv[1]); + printf("err_arg=%d\n", err_arg); + return 1; + } + + if (nbargs > 0) { + int arg; + + for (arg = 0; arg < nbargs; arg++) + printf("arg %d: type=%d, int=0x%08x\n", + arg, argp[arg].type, *(int*)&argp[arg].data.uint); + } + return 0; +}