diff --git a/doc/configuration.txt b/doc/configuration.txt index 4043e5c0b..208092863 100644 --- a/doc/configuration.txt +++ b/doc/configuration.txt @@ -12292,6 +12292,11 @@ If an unknown command is sent, haproxy displays the usage message which reminds all supported commands. Some commands support a more complex syntax, generally it will explain what part of the command is invalid when this happens. +add map + Add an entry into the map to associate the value to the key + . This command does not verify if the entry already exists. It is + mainly used to fill a map after a clear operation. + clear counters Clear the max values of the statistics counters in each proxy (frontend & backend) and in each server. The cumulated counters are not affected. This @@ -12304,6 +12309,9 @@ clear counters all server. This has the same effect as restarting. This command is restricted and can only be issued on sockets configured for level "admin". +clear map + Remove all entries from the map . + clear table [ data. ] | [ key ] Remove entries from the stick-table
. @@ -12374,6 +12382,9 @@ enable agent / This command is restricted and can only be issued on sockets configured for level "admin". +del map + Delete all the map entries from the map corresponding to the key . + disable frontend Mark the frontend as temporarily stopped. This corresponds to the mode which is used during a soft restart : the frontend releases the port but can be @@ -12462,6 +12473,10 @@ prompt quit Close the connection when in interactive mode. +set map + Modify the value corresponding to each key in a map . The new value + is . + set maxconn frontend Dynamically change the specified frontend's maxconn setting. Any positive value is allowed including zero, but setting values larger than the global @@ -12577,6 +12592,10 @@ show errors [] show info Dump info about haproxy status on current process. +show map [] + Dump info about map converters. Without argument, the list of all available + maps is returned. If a is specified, its contents are dumped. + show sess Dump all known sessions. Avoid doing this on slow connections as this can be huge. This command is restricted and can only be issued on sockets diff --git a/include/proto/dumpstats.h b/include/proto/dumpstats.h index fd345d51a..0da3091c5 100644 --- a/include/proto/dumpstats.h +++ b/include/proto/dumpstats.h @@ -39,20 +39,24 @@ #define STATS_TYPE_SO 3 /* unix stats socket states */ -#define STAT_CLI_INIT 0 /* initial state, must leave to zero ! */ -#define STAT_CLI_END 1 /* final state, let's close */ -#define STAT_CLI_GETREQ 2 /* wait for a request */ -#define STAT_CLI_OUTPUT 3 /* all states after this one are responses */ -#define STAT_CLI_PROMPT 3 /* display the prompt (first output, same code) */ -#define STAT_CLI_PRINT 4 /* display message in cli->msg */ +#define STAT_CLI_INIT 0 /* initial state, must leave to zero ! */ +#define STAT_CLI_END 1 /* final state, let's close */ +#define STAT_CLI_GETREQ 2 /* wait for a request */ +#define STAT_CLI_OUTPUT 3 /* all states after this one are responses */ +#define STAT_CLI_PROMPT 3 /* display the prompt (first output, same code) */ +#define STAT_CLI_PRINT 4 /* display message in cli->msg */ -#define STAT_CLI_O_INFO 5 /* dump info */ -#define STAT_CLI_O_SESS 6 /* dump sessions */ -#define STAT_CLI_O_ERR 7 /* dump errors */ -#define STAT_CLI_O_TAB 8 /* dump tables */ -#define STAT_CLI_O_CLR 9 /* clear tables */ -#define STAT_CLI_O_SET 10 /* set entries in tables */ -#define STAT_CLI_O_STAT 11 /* dump stats */ +#define STAT_CLI_O_INFO 5 /* dump info */ +#define STAT_CLI_O_SESS 6 /* dump sessions */ +#define STAT_CLI_O_ERR 7 /* dump errors */ +#define STAT_CLI_O_TAB 8 /* dump tables */ +#define STAT_CLI_O_CLR 9 /* clear tables */ +#define STAT_CLI_O_SET 10 /* set entries in tables */ +#define STAT_CLI_O_STAT 11 /* dump stats */ + +#define STAT_CLI_O_MAPS 12 /* list all maps */ +#define STAT_CLI_O_MAP 13 /* list all map entries of a map */ +#define STAT_CLI_O_MLOOK 14 /* lookup a map entry */ /* HTTP stats : applet.st0 */ enum { diff --git a/include/types/stream_interface.h b/include/types/stream_interface.h index f0b63c485..16c127d1b 100644 --- a/include/types/stream_interface.h +++ b/include/types/stream_interface.h @@ -141,6 +141,12 @@ struct appctx { struct { void *ptr; /* multi-purpose pointer for peers */ } peers; + struct { + struct map_reference *ref; + struct map_entry *ent; + struct map_descriptor *desc; + struct chunk chunk; + } map; } ctx; /* used by stats I/O handlers to dump the stats */ }; diff --git a/src/dumpstats.c b/src/dumpstats.c index 96c5cead4..c63464d9b 100644 --- a/src/dumpstats.c +++ b/src/dumpstats.c @@ -46,8 +46,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -68,6 +70,9 @@ static int stats_dump_errors_to_buffer(struct stream_interface *si); static int stats_table_request(struct stream_interface *si, int show); static int stats_dump_proxy_to_buffer(struct stream_interface *si, struct proxy *px, struct uri_auth *uri); static int stats_dump_stat_to_buffer(struct stream_interface *si, struct uri_auth *uri); +static int stats_maps_list(struct stream_interface *si); +static int stats_map_list(struct stream_interface *si); +static int stats_map_lookup(struct stream_interface *si); /* * cli_io_handler() @@ -103,6 +108,7 @@ static const char stats_sock_usage_msg[] = "Unknown command. Please enter one of the following commands only :\n" " clear counters : clear max statistics counters (add 'all' for all counters)\n" " clear table : remove an entry from a table\n" + " clear map [id] : clear the content of this map\n" " help : this message\n" " prompt : toggle interactive mode with prompt\n" " quit : disconnect\n" @@ -111,12 +117,16 @@ static const char stats_sock_usage_msg[] = " show errors : report last request and response errors for each proxy\n" " show sess [id] : report the list of current sessions or dump this session\n" " show table [id]: report table usage stats or dump this table's contents\n" + " show map [id] : report avalaible maps or dump this map's contents\n" " get weight : report a server's current weight\n" " set weight : change a server's weight\n" " set table [id] : update or create a table entry's data\n" " set timeout : change a timeout setting\n" " set maxconn : change a maxconn setting\n" " set rate-limit : change a rate limiting value\n" + " set map [id] [key] [value] : modify map entry\n" + " add map [id] [key] [value] : add map entry\n" + " del map [id] [key] : delete map entry\n" " disable : put a server or frontend in maintenance mode\n" " enable : re-enable a server or frontend which is in maintenance mode\n" " shutdown : kill a session or a frontend (eg:to release listening ports)\n" @@ -914,6 +924,42 @@ static struct server *expect_server_admin(struct session *s, struct stream_inter return sv; } +/* This function is used with map management. It permits to browse each + * really allocated descriptors of one map reference. The variable + * ctx.map.ref> must contain the map reference to browse. + * The variable ctx.map.desc> contain the descriptor of the + * current allocated map descriptor. This variable must be initialized + * to NULL. + */ +static inline void stats_map_lookup_next(struct stream_interface *si) +{ + struct appctx *appctx = __objt_appctx(si->end); + + /* search the next allocated map */ + while (1) { + /* get next descriptor */ + if (!appctx->ctx.map.desc) + appctx->ctx.map.desc = LIST_NEXT(&appctx->ctx.map.ref->maps, + struct map_descriptor *, list); + else + appctx->ctx.map.desc = LIST_NEXT(&appctx->ctx.map.desc->list, + struct map_descriptor *, list); + + /* detect end of list */ + if (&appctx->ctx.map.desc->list == &appctx->ctx.map.ref->maps) { + appctx->ctx.map.desc = NULL; + return; + } + + /* do not lookup this entry */ + if (!appctx->ctx.map.desc->do_free) + continue; + + /* avalaible descriptor */ + return; + } +} + /* Processes the stats interpreter on the statistics socket. This function is * called from an applet running in a stream interface. The function returns 1 * if the request was understood, otherwise zero. It sets appctx->st0 to a value @@ -1021,6 +1067,25 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) else if (strcmp(args[1], "table") == 0) { stats_sock_table_request(si, args, STAT_CLI_O_TAB); } + else if (strcmp(args[1], "map") == 0) { + + /* no parameter: display all map avalaible */ + if (!*args[2]) { + appctx->st2 = STAT_ST_INIT; + appctx->st0 = STAT_CLI_O_MAPS; + return 1; + } + + /* lookup into the maps */ + appctx->ctx.map.ref = map_get_reference(args[2]); + if (!appctx->ctx.map.ref) { + appctx->ctx.cli.msg = "Unknown map reference.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + appctx->st2 = STAT_ST_INIT; + appctx->st0 = STAT_CLI_O_MAP; + } else { /* neither "stat" nor "info" nor "sess" nor "errors" nor "table" */ return 0; } @@ -1088,6 +1153,43 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) /* end of processing */ return 1; } + else if (strcmp(args[1], "map") == 0) { + struct map_reference *mref; + struct map_descriptor *mdesc; + struct map_entry *ent, *nent; + + /* no parameter */ + if (!*args[2]) { + appctx->ctx.cli.msg = "Expect map reference.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* lookup into the maps */ + mref = map_get_reference(args[2]); + if (!mref) { + appctx->ctx.cli.msg = "Unknown map reference.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* clear all maps */ + list_for_each_entry(mdesc, &mref->maps, list) + if (mdesc->do_free) + pattern_prune_expr(mdesc->pat); + + /* clear map reference */ + list_for_each_entry_safe(ent, nent, &mref->entries, list) { + LIST_DEL(&ent->list); + free(ent->key); + free(ent->value); + free(ent); + } + + /* return response */ + appctx->ctx.cli.msg = "Done.\n"; + appctx->st0 = STAT_CLI_PRINT; + } else { /* unknown "clear" argument */ return 0; @@ -1122,6 +1224,37 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) bi_putstr(si->ib, trash.str); return 1; } + else if (strcmp(args[1], "map") == 0) { + + /* no parameter */ + if (!*args[2] || !*args[3]) { + appctx->ctx.cli.msg = "Expect map reference and required key.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* lookup into the maps */ + appctx->ctx.map.ref = map_get_reference(args[2]); + if (!appctx->ctx.map.ref) { + appctx->ctx.cli.msg = "Unknown map reference.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* copy input string */ + appctx->ctx.map.chunk.len = strlen(args[3]); + appctx->ctx.map.chunk.size = appctx->ctx.map.chunk.len + 1; + appctx->ctx.map.chunk.str = strdup(args[3]); + if (!appctx->ctx.map.chunk.str) { + appctx->ctx.cli.msg = "Out of memory error.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* prepare response */ + appctx->st2 = STAT_ST_INIT; + appctx->st0 = STAT_CLI_O_MLOOK; + } else { /* not "get weight" */ return 0; } @@ -1319,6 +1452,70 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) else if (strcmp(args[1], "table") == 0) { stats_sock_table_request(si, args, STAT_CLI_O_SET); } + else if (strcmp(args[1], "map") == 0) { + struct pattern *pat_elt; + struct pat_idx_elt *idx_elt; + char *value; + + /* Expect three parameters: map name, key and new value. */ + if (!*args[2] || !*args[3] || !*args[4]) { + appctx->ctx.cli.msg = "'set map' expect three parameters: map name, key and value.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* Lookup the reference in the maps. */ + appctx->ctx.map.ref = map_get_reference(args[2]); + if (!appctx->ctx.map.ref) { + appctx->ctx.cli.msg = "Unknown map reference.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* Lookup the entry in the reference values. */ + list_for_each_entry(appctx->ctx.map.ent, &appctx->ctx.map.ref->entries, list) + if (strcmp(args[3], appctx->ctx.map.ent->key) == 0) + break; + + if (&appctx->ctx.map.ent->list == &appctx->ctx.map.ref->entries) { + appctx->ctx.cli.msg = "Entry not found.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* Update each reference entries. */ + list_for_each_entry(appctx->ctx.map.ent, &appctx->ctx.map.ref->entries, list) { + if (strcmp(args[3], appctx->ctx.map.ent->key) == 0) { + value = strdup(args[4]); + if (!value) { + appctx->ctx.cli.msg = "Out of memory error.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + free(appctx->ctx.map.ent->value); + appctx->ctx.map.ent->value = value; + } + } + + /* Change the sample. The lookup juste return the first entry, other + * entries are not changed, but are never matched. + */ + appctx->ctx.map.desc = NULL; + for (stats_map_lookup_next(si); + appctx->ctx.map.desc; + stats_map_lookup_next(si)) { + pattern_lookup(args[3], appctx->ctx.map.desc->pat, &pat_elt, &idx_elt, NULL); + if (pat_elt != NULL) + appctx->ctx.map.desc->parse(appctx->ctx.map.ent->value, pat_elt->smp); + if (idx_elt != NULL) + appctx->ctx.map.desc->parse(appctx->ctx.map.ent->value, idx_elt->smp); + } + + /* The set is done, send message. */ + appctx->ctx.cli.msg = "Done.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } else { /* unknown "set" parameter */ return 0; } @@ -1536,6 +1733,190 @@ static int stats_sock_parse_request(struct stream_interface *si, char *line) return 1; } } + else if (strcmp(args[0], "del") == 0) { + if (strcmp(args[1], "map") == 0) { + struct pattern *pat_elt; + struct pat_idx_elt *idx_elt; + struct map_entry *ent; + + /* Expect two parameters: map name and key. */ + if (!*args[2] || !*args[3]) { + appctx->ctx.cli.msg = "'del map' expect two parameters: map name and key.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* Lookup the reference in the maps. */ + appctx->ctx.map.ref = map_get_reference(args[2]); + if (!appctx->ctx.map.ref) { + appctx->ctx.cli.msg = "Unknown map reference.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* Lookup the entry in the reference values. + * If the entry is not found in the reference, return error message. + */ + list_for_each_entry(appctx->ctx.map.ent, &appctx->ctx.map.ref->entries, list) + if (strcmp(args[3], appctx->ctx.map.ent->key) == 0) + break; + + if (&appctx->ctx.map.ent->list == &appctx->ctx.map.ref->entries) { + appctx->ctx.cli.msg = "Entry not found.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* Delete each enties from reference. */ + list_for_each_entry_safe(appctx->ctx.map.ent, ent, &appctx->ctx.map.ref->entries, list) { + if (strcmp(args[3], appctx->ctx.map.ent->key) == 0) { + LIST_DEL(&appctx->ctx.map.ent->list); + free(appctx->ctx.map.ent->key); + free(appctx->ctx.map.ent->value); + free(appctx->ctx.map.ent); + } + } + + /* Delete all matching entries for each map descritor. */ + appctx->ctx.map.desc = NULL; + stats_map_lookup_next(si); + while (appctx->ctx.map.desc) { + while (pattern_lookup(args[3], appctx->ctx.map.desc->pat, &pat_elt, &idx_elt, NULL)) { + if (pat_elt != NULL) { + LIST_DEL(&pat_elt->list); + pattern_free(pat_elt); + } + if (idx_elt != NULL) { + ebmb_delete(&idx_elt->node); + free(idx_elt); + } + } + stats_map_lookup_next(si); + } + + /* The deletion is done, send message. */ + appctx->ctx.cli.msg = "Done.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + else { /* unknown "del" parameter */ + appctx->ctx.cli.msg = "'del' only supports 'map'.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + } + else if (strcmp(args[0], "add") == 0) { + if (strcmp(args[1], "map") == 0) { + const char *params[2]; + struct pattern *pat; + struct map_entry *ent; + struct sample_storage *smp; + + /* Expect three parameters: map name, key and new value. */ + if (!*args[2] || !*args[3] || !*args[4]) { + appctx->ctx.cli.msg = "'add map' expect three parameters: map name, key and value.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + params[0] = args[3]; + params[1] = ""; + + /* Lookup the reference in the maps. */ + appctx->ctx.map.ref = map_get_reference(args[2]); + if (!appctx->ctx.map.ref) { + appctx->ctx.cli.msg = "Unknown map reference.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* Prepare and link the new map_entry element. If out of memory + * error the action is cancelled and the descriptor are left + * coherents. + */ + ent = malloc(sizeof(*ent)); + if (!ent) { + appctx->ctx.cli.msg = "Out of memory error.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + ent->key = strdup(args[3]); + if (!ent->key) { + free(ent); + appctx->ctx.cli.msg = "Out of memory error.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + ent->value = strdup(args[4]); + if (!ent->value) { + free(ent->key); + free(ent); + appctx->ctx.cli.msg = "Out of memory error.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + LIST_ADDQ(&appctx->ctx.map.ref->entries, &ent->list); + + /* Browse each map descritor and try to insert this new value. */ + appctx->ctx.map.desc = NULL; + for (stats_map_lookup_next(si); + appctx->ctx.map.desc; + stats_map_lookup_next(si)) { + + /* Create new sample. Return out of memory error + * if the memory cannot be allocated. The 'add' process + * is aborted, but the already inserted entries are not + * deleted. + */ + smp = calloc(1, sizeof(*smp)); + if (!smp) { + appctx->ctx.cli.msg = "Out of memory error. The value is not added in all maps.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + + /* Create sample. If this function fails, the insertion + * is canceled for this 'descriptor', but continue, for + * the other descriptors. + */ + if (!appctx->ctx.map.desc->parse(ent->value, smp)) { + free(smp); + continue; + } + + /* If the value can be indexed, get the first pattern. If + * the return entry is not the indexed entry, new 'pattern' is + * created by the function pattern_register(). If the 'pattern' + * is NULL, new entry is created. This is ugly because the + * following code interfers with the own code of the function + * pattern_register(). + */ + if (appctx->ctx.map.desc->pat->match == pat_match_str || + appctx->ctx.map.desc->pat->match == pat_match_ip) { + pat = LIST_NEXT(&appctx->ctx.map.desc->pat->patterns, struct pattern *, list); + if (&pat->list == &appctx->ctx.map.desc->pat->patterns) + pat = NULL; + } + else + pat = NULL; + + if (!pattern_register(appctx->ctx.map.desc->pat, params, smp, &pat, 0, NULL)) { + free(smp); + continue; + } + } + + /* The add is done, send message. */ + appctx->ctx.cli.msg = "Done.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + else { /* unknown "del" parameter */ + appctx->ctx.cli.msg = "'add' only supports 'map'.\n"; + appctx->st0 = STAT_CLI_PRINT; + return 1; + } + } else { /* not "show" nor "clear" nor "get" nor "set" nor "enable" nor "disable" */ return 0; } @@ -1676,6 +2057,17 @@ static void cli_io_handler(struct stream_interface *si) if (stats_table_request(si, appctx->st0)) appctx->st0 = STAT_CLI_PROMPT; break; + case STAT_CLI_O_MAPS: + if (stats_maps_list(si)) + appctx->st0 = STAT_CLI_PROMPT; + break; + case STAT_CLI_O_MAP: + if (stats_map_list(si)) + appctx->st0 = STAT_CLI_PROMPT; + break; + case STAT_CLI_O_MLOOK: + if (stats_map_lookup(si)) + appctx->st0 = STAT_CLI_PROMPT; default: /* abnormal state */ appctx->st0 = STAT_CLI_PROMPT; break; @@ -4222,6 +4614,252 @@ static int stats_dump_full_sess_to_buffer(struct stream_interface *si, struct se return 1; } +static int stats_maps_list(struct stream_interface *si) +{ + struct appctx *appctx = __objt_appctx(si->end); + + switch (appctx->st2) { + case STAT_ST_INIT: + /* Init to the first entry. The list cannot be change */ + appctx->ctx.map.ref = LIST_NEXT(&maps, struct map_reference *, list); + appctx->st2 = STAT_ST_LIST; + /* fall through */ + + case STAT_ST_LIST: + while (appctx->ctx.map.ref) { + + chunk_reset(&trash); + + /* build messages */ + chunk_appendf(&trash, "%s\n", appctx->ctx.map.ref->reference); + + if (bi_putchk(si->ib, &trash) == -1) { + /* let's try again later from this session. We add ourselves into + * this session's users so that it can remove us upon termination. + */ + return 0; + } + + /* get next list entry and check the end of the list */ + appctx->ctx.map.ref = LIST_NEXT(&appctx->ctx.map.ref->list, + struct map_reference *, list); + if (&appctx->ctx.map.ref->list == &maps) + break; + } + + appctx->st2 = STAT_ST_FIN; + /* fall through */ + + default: + appctx->st2 = STAT_ST_FIN; + return 1; + } +} + +static const char *smp_to_type[SMP_TYPES] = { + [SMP_T_BOOL] = "bool", + [SMP_T_UINT] = "uint", + [SMP_T_SINT] = "sint", + [SMP_T_ADDR] = "addr", + [SMP_T_IPV4] = "ipv4", + [SMP_T_IPV6] = "ipv6", + [SMP_T_STR] = "str", + [SMP_T_BIN] = "bin", + [SMP_T_CSTR] = "cstr", + [SMP_T_CBIN] = "cbin", +}; + +static int stats_map_lookup(struct stream_interface *si) +{ + struct appctx *appctx = __objt_appctx(si->end); + struct sample_storage *smp; + struct sample sample; + struct pattern *pat; + struct pat_idx_elt *elt; + enum pat_match_res res; + struct sockaddr_in addr; + char s_addr[INET_ADDRSTRLEN]; + + switch (appctx->st2) { + case STAT_ST_INIT: + appctx->ctx.map.desc = NULL; + stats_map_lookup_next(si); + appctx->st2 = STAT_ST_LIST; + /* fall through */ + + case STAT_ST_LIST: + /* for each lookup type */ + while (appctx->ctx.map.desc) { + /* initialise chunk to build new message */ + chunk_reset(&trash); + + /* execute pattern matching */ + sample.type = SMP_T_CSTR; + sample.data.str.len = appctx->ctx.map.chunk.len; + sample.data.str.str = appctx->ctx.map.chunk.str; + pat = NULL; + elt = NULL; + res = pattern_exec_match(appctx->ctx.map.desc->pat, &sample, &smp, &pat, &elt); + + /* build return message: set type of match */ + /**/ if (appctx->ctx.map.desc->pat->match == NULL) + chunk_appendf(&trash, "found, "); + else if (appctx->ctx.map.desc->pat->match == pat_match_nothing) + chunk_appendf(&trash, "bool, "); + else if (appctx->ctx.map.desc->pat->match == pat_match_int) + chunk_appendf(&trash, "int, "); + else if (appctx->ctx.map.desc->pat->match == pat_match_ip) + chunk_appendf(&trash, "ip, "); + else if (appctx->ctx.map.desc->pat->match == pat_match_bin) + chunk_appendf(&trash, "bin, "); + else if (appctx->ctx.map.desc->pat->match == pat_match_len) + chunk_appendf(&trash, "len, "); + else if (appctx->ctx.map.desc->pat->match == pat_match_str) + chunk_appendf(&trash, "str, "); + else if (appctx->ctx.map.desc->pat->match == pat_match_beg) + chunk_appendf(&trash, "beg, "); + else if (appctx->ctx.map.desc->pat->match == pat_match_sub) + chunk_appendf(&trash, "sub, "); + else if (appctx->ctx.map.desc->pat->match == pat_match_dir) + chunk_appendf(&trash, "dir, "); + else if (appctx->ctx.map.desc->pat->match == pat_match_dom) + chunk_appendf(&trash, "dom, "); + else if (appctx->ctx.map.desc->pat->match == pat_match_end) + chunk_appendf(&trash, "end, "); + else if (appctx->ctx.map.desc->pat->match == pat_match_reg) + chunk_appendf(&trash, "reg, "); + else /* The never appens case */ + chunk_appendf(&trash, "unknown(%p), ", appctx->ctx.map.desc->pat->match); + + /* Display no match, and set default value */ + if (res == PAT_NOMATCH) { + chunk_appendf(&trash, "no-match, "); + smp = appctx->ctx.map.desc->def; + } + + /* Display match and match info */ + else { + /* display match */ + chunk_appendf(&trash, "match, "); + + /* display search mode */ + if (elt) + chunk_appendf(&trash, "tree, "); + else + chunk_appendf(&trash, "list, "); + + /* display search options */ + if (pat) { + /* case sensitive */ + if (pat->flags & PAT_F_IGNORE_CASE) + chunk_appendf(&trash, "case-insensitive, "); + else + chunk_appendf(&trash, "case-sensitive, "); + + /* display source */ + if (pat->flags & PAT_F_FROM_FILE) + chunk_appendf(&trash, "from-file, "); + } + + /* display match expresion */ + if (elt) { + if (appctx->ctx.map.desc->pat->match == pat_match_str) { + chunk_appendf(&trash, "match=\"%s\", ", elt->node.key); + } + /* only IPv4 */ + else if (appctx->ctx.map.desc->pat->match == pat_match_ip) { + /* convert ip */ + memcpy(&addr.sin_addr, elt->node.key, 4); + addr.sin_family = AF_INET; + if (addr_to_str((struct sockaddr_storage *)&addr, s_addr, INET_ADDRSTRLEN)) + chunk_appendf(&trash, "match=\"%s/%d\", ", s_addr, elt->node.node.pfx); + } + } + } + + /* display return value */ + if (!smp) { + chunk_appendf(&trash, "return=nothing\n"); + } + else { + memcpy(&sample.data, &smp->data, sizeof(sample.data)); + sample.type = smp->type; + if (sample_casts[sample.type][SMP_T_CSTR] && + sample_casts[sample.type][SMP_T_CSTR](&sample)) + chunk_appendf(&trash, "return=\"%s\", type=\"%s\"\n", + sample.data.str.str, smp_to_type[smp->type]); + else + chunk_appendf(&trash, "return=cannot-display, type=\"%s\"\n", + smp_to_type[smp->type]); + } + + /* display response */ + if (bi_putchk(si->ib, &trash) == -1) { + /* let's try again later from this session. We add ourselves into + * this session's users so that it can remove us upon termination. + */ + return 0; + } + + /* get next entry */ + stats_map_lookup_next(si); + } + + appctx->st2 = STAT_ST_FIN; + /* fall through */ + + default: + appctx->st2 = STAT_ST_FIN; + free(appctx->ctx.map.chunk.str); + return 1; + } +} + +static int stats_map_list(struct stream_interface *si) +{ + struct appctx *appctx = __objt_appctx(si->end); + + switch (appctx->st2) { + + case STAT_ST_INIT: + /* Init to the first entry. The list cannot be change */ + appctx->ctx.map.ent = LIST_NEXT(&appctx->ctx.map.ref->entries, + struct map_entry *, list); + if (&appctx->ctx.map.ent->list == &appctx->ctx.map.ref->entries) + appctx->ctx.map.ent = NULL; + appctx->st2 = STAT_ST_LIST; + /* fall through */ + + case STAT_ST_LIST: + while (appctx->ctx.map.ent) { + chunk_reset(&trash); + + /* build messages */ + chunk_appendf(&trash, "%s %s\n", appctx->ctx.map.ent->key, appctx->ctx.map.ent->value); + + if (bi_putchk(si->ib, &trash) == -1) { + /* let's try again later from this session. We add ourselves into + * this session's users so that it can remove us upon termination. + */ + return 0; + } + + /* get next list entry and check the end of the list */ + appctx->ctx.map.ent = LIST_NEXT(&appctx->ctx.map.ent->list, + struct map_entry *, list); + if (&appctx->ctx.map.ent->list == &appctx->ctx.map.ref->entries) + break; + } + + appctx->st2 = STAT_ST_FIN; + /* fall through */ + + default: + appctx->st2 = STAT_ST_FIN; + return 1; + } +} + /* This function dumps all sessions' states onto the stream interface's * read buffer. It returns 0 if the output buffer is full and it needs * to be called again, otherwise non-zero. It is designed to be called