| /* |
| * Functions used to parse typed argument lists |
| * |
| * Copyright 2012 Willy Tarreau <w@1wt.eu> |
| * |
| * 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 <sys/types.h> |
| #include <sys/socket.h> |
| #include <arpa/inet.h> |
| |
| #include <common/standard.h> |
| #include <proto/arg.h> |
| |
| 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 |
| * <err_msg> 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 <beg> and <in> (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 empty_err; |
| else if (*beg < '0' || *beg > '9') { |
| beg++; |
| arg->data.sint = read_uint(&beg, in); |
| if (beg < in) |
| goto parse_err; |
| if (*word == '-') |
| arg->data.sint = -arg->data.sint; |
| else if (*word != '+') // 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 empty_err; |
| |
| arg->data.uint = read_uint(&beg, in); |
| if (beg < in) |
| goto parse_err; |
| break; |
| |
| case ARGT_FE: |
| case ARGT_BE: |
| case ARGT_TAB: |
| case ARGT_SRV: |
| case ARGT_USR: |
| /* These argument types need to be stored as strings during |
| * parsing then resolved later. |
| */ |
| arg->unresolved = 1; |
| /* fall through */ |
| 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 empty_err; |
| |
| if (inet_pton(AF_INET, word, &arg->data.ipv4) <= 0) |
| goto parse_err; |
| break; |
| |
| case ARGT_MSK4: |
| if (in == beg) // empty mask |
| goto empty_err; |
| |
| if (!str2mask(word, &arg->data.ipv4)) |
| goto parse_err; |
| |
| arg->type = ARGT_IPV4; |
| break; |
| |
| case ARGT_IPV6: |
| if (in == beg) // empty address |
| goto empty_err; |
| |
| if (inet_pton(AF_INET6, word, &arg->data.ipv6) <= 0) |
| goto parse_err; |
| break; |
| |
| case ARGT_MSK6: /* not yet implemented */ |
| goto not_impl; |
| |
| case ARGT_TIME: |
| if (in == beg) // empty time |
| goto empty_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 empty_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 not_impl; |
| } |
| |
| 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 <in> */ |
| if (err_msg) { |
| /* the caller is responsible for freeing this message */ |
| word = my_strndup(in, len); |
| memprintf(err_msg, "end of arguments expected at position %d, but got '%s'", |
| pos + 1, 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; |
| |
| err: |
| free(word); |
| free(arg_list); |
| if (err_arg) |
| *err_arg = pos; |
| if (err_ptr) |
| *err_ptr = in; |
| return -1; |
| |
| empty_err: |
| if (err_msg) { |
| memprintf(err_msg, "expected type '%s' at position %d, but got nothing", |
| arg_type_names[(mask >> (pos * 4)) & 15], pos + 1); |
| } |
| goto err; |
| |
| parse_err: |
| if (err_msg) { |
| memprintf(err_msg, "failed to parse '%s' as type '%s' at position %d", |
| word, arg_type_names[(mask >> (pos * 4)) & 15], pos + 1); |
| } |
| goto err; |
| |
| not_impl: |
| if (err_msg) { |
| memprintf(err_msg, "parsing for type '%s' was not implemented, please report this bug", |
| arg_type_names[(mask >> (pos * 4)) & 15]); |
| } |
| goto err; |
| } |