diff options
Diffstat (limited to '')
-rw-r--r-- | external/unbound/testcode/replay.c | 1026 |
1 files changed, 1026 insertions, 0 deletions
diff --git a/external/unbound/testcode/replay.c b/external/unbound/testcode/replay.c new file mode 100644 index 000000000..ee87b1a88 --- /dev/null +++ b/external/unbound/testcode/replay.c @@ -0,0 +1,1026 @@ +/* + * testcode/replay.c - store and use a replay of events for the DNS resolver. + * + * Copyright (c) 2007, NLnet Labs. All rights reserved. + * + * This software is open source. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * Neither the name of the NLNET LABS nor the names of its contributors may + * be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * \file + * Store and use a replay of events for the DNS resolver. + * Used to test known scenarios to get known outcomes. + */ + +#include "config.h" +/* for strtod prototype */ +#include <math.h> +#include <ctype.h> +#include <time.h> +#include "util/log.h" +#include "util/net_help.h" +#include "util/config_file.h" +#include "testcode/replay.h" +#include "testcode/testpkts.h" +#include "testcode/fake_event.h" +#include "ldns/str2wire.h" + +/** max length of lines in file */ +#define MAX_LINE_LEN 10240 + +/** + * Expand a macro + * @param store: value storage + * @param runtime: replay runtime for other stuff. + * @param text: the macro text, after the ${, Updated to after the } when + * done (successfully). + * @return expanded text, malloced. NULL on failure. + */ +static char* macro_expand(rbtree_t* store, + struct replay_runtime* runtime, char** text); + +/** compare of time values */ +static int +timeval_smaller(const struct timeval* x, const struct timeval* y) +{ +#ifndef S_SPLINT_S + if(x->tv_sec < y->tv_sec) + return 1; + else if(x->tv_sec == y->tv_sec) { + if(x->tv_usec <= y->tv_usec) + return 1; + else return 0; + } + else return 0; +#endif +} + +/** parse keyword in string. + * @param line: if found, the line is advanced to after the keyword. + * @param keyword: string. + * @return: true if found, false if not. + */ +static int +parse_keyword(char** line, const char* keyword) +{ + size_t len = (size_t)strlen(keyword); + if(strncmp(*line, keyword, len) == 0) { + *line += len; + return 1; + } + return 0; +} + +/** delete moment */ +static void +replay_moment_delete(struct replay_moment* mom) +{ + if(!mom) + return; + if(mom->match) { + delete_entry(mom->match); + } + free(mom->autotrust_id); + free(mom->string); + free(mom->variable); + config_delstrlist(mom->file_content); + free(mom); +} + +/** delete range */ +static void +replay_range_delete(struct replay_range* rng) +{ + if(!rng) + return; + delete_entry(rng->match); + free(rng); +} + +/** strip whitespace from end of string */ +static void +strip_end_white(char* p) +{ + size_t i; + for(i = strlen(p); i > 0; i--) { + if(isspace((int)p[i-1])) + p[i-1] = 0; + else return; + } +} + +/** + * Read a range from file. + * @param remain: Rest of line (after RANGE keyword). + * @param in: file to read from. + * @param name: name to print in errors. + * @param pstate: read state structure with + * with lineno : incremented as lines are read. + * ttl, origin, prev for readentry. + * @param line: line buffer. + * @return: range object to add to list, or NULL on error. + */ +static struct replay_range* +replay_range_read(char* remain, FILE* in, const char* name, + struct sldns_file_parse_state* pstate, char* line) +{ + struct replay_range* rng = (struct replay_range*)malloc( + sizeof(struct replay_range)); + off_t pos; + char *parse; + struct entry* entry, *last = NULL; + if(!rng) + return NULL; + memset(rng, 0, sizeof(*rng)); + /* read time range */ + if(sscanf(remain, " %d %d", &rng->start_step, &rng->end_step)!=2) { + log_err("Could not read time range: %s", line); + free(rng); + return NULL; + } + /* read entries */ + pos = ftello(in); + while(fgets(line, MAX_LINE_LEN-1, in)) { + pstate->lineno++; + parse = line; + while(isspace((int)*parse)) + parse++; + if(!*parse || *parse == ';') { + pos = ftello(in); + continue; + } + if(parse_keyword(&parse, "ADDRESS")) { + while(isspace((int)*parse)) + parse++; + strip_end_white(parse); + if(!extstrtoaddr(parse, &rng->addr, &rng->addrlen)) { + log_err("Line %d: could not read ADDRESS: %s", + pstate->lineno, parse); + free(rng); + return NULL; + } + pos = ftello(in); + continue; + } + if(parse_keyword(&parse, "RANGE_END")) { + return rng; + } + /* set position before line; read entry */ + pstate->lineno--; + fseeko(in, pos, SEEK_SET); + entry = read_entry(in, name, pstate, 1); + if(!entry) + fatal_exit("%d: bad entry", pstate->lineno); + entry->next = NULL; + if(last) + last->next = entry; + else rng->match = entry; + last = entry; + + pos = ftello(in); + } + replay_range_delete(rng); + return NULL; +} + +/** Read FILE match content */ +static void +read_file_content(FILE* in, int* lineno, struct replay_moment* mom) +{ + char line[MAX_LINE_LEN]; + char* remain = line; + struct config_strlist** last = &mom->file_content; + line[MAX_LINE_LEN-1]=0; + if(!fgets(line, MAX_LINE_LEN-1, in)) + fatal_exit("FILE_BEGIN expected at line %d", *lineno); + if(!parse_keyword(&remain, "FILE_BEGIN")) + fatal_exit("FILE_BEGIN expected at line %d", *lineno); + while(fgets(line, MAX_LINE_LEN-1, in)) { + (*lineno)++; + if(strncmp(line, "FILE_END", 8) == 0) { + return; + } + if(line[0]) line[strlen(line)-1] = 0; /* remove newline */ + if(!cfg_strlist_insert(last, strdup(line))) + fatal_exit("malloc failure"); + last = &( (*last)->next ); + } + fatal_exit("no FILE_END in input file"); +} + +/** read assign step info */ +static void +read_assign_step(char* remain, struct replay_moment* mom) +{ + char buf[1024]; + char eq; + int skip; + buf[sizeof(buf)-1]=0; + if(sscanf(remain, " %1023s %c %n", buf, &eq, &skip) != 2) + fatal_exit("cannot parse assign: %s", remain); + mom->variable = strdup(buf); + if(eq != '=') + fatal_exit("no '=' in assign: %s", remain); + remain += skip; + if(remain[0]) remain[strlen(remain)-1]=0; /* remove newline */ + mom->string = strdup(remain); + if(!mom->variable || !mom->string) + fatal_exit("out of memory"); +} + +/** + * Read a replay moment 'STEP' from file. + * @param remain: Rest of line (after STEP keyword). + * @param in: file to read from. + * @param name: name to print in errors. + * @param pstate: with lineno, ttl, origin, prev for parse state. + * lineno is incremented. + * @return: range object to add to list, or NULL on error. + */ +static struct replay_moment* +replay_moment_read(char* remain, FILE* in, const char* name, + struct sldns_file_parse_state* pstate) +{ + struct replay_moment* mom = (struct replay_moment*)malloc( + sizeof(struct replay_moment)); + int skip = 0; + int readentry = 0; + if(!mom) + return NULL; + memset(mom, 0, sizeof(*mom)); + if(sscanf(remain, " %d%n", &mom->time_step, &skip) != 1) { + log_err("%d: cannot read number: %s", pstate->lineno, remain); + free(mom); + return NULL; + } + remain += skip; + while(isspace((int)*remain)) + remain++; + if(parse_keyword(&remain, "NOTHING")) { + mom->evt_type = repevt_nothing; + } else if(parse_keyword(&remain, "QUERY")) { + mom->evt_type = repevt_front_query; + readentry = 1; + if(!extstrtoaddr("127.0.0.1", &mom->addr, &mom->addrlen)) + fatal_exit("internal error"); + } else if(parse_keyword(&remain, "CHECK_ANSWER")) { + mom->evt_type = repevt_front_reply; + readentry = 1; + } else if(parse_keyword(&remain, "CHECK_OUT_QUERY")) { + mom->evt_type = repevt_back_query; + readentry = 1; + } else if(parse_keyword(&remain, "REPLY")) { + mom->evt_type = repevt_back_reply; + readentry = 1; + } else if(parse_keyword(&remain, "TIMEOUT")) { + mom->evt_type = repevt_timeout; + } else if(parse_keyword(&remain, "TIME_PASSES")) { + mom->evt_type = repevt_time_passes; + while(isspace((int)*remain)) + remain++; + if(parse_keyword(&remain, "EVAL")) { + while(isspace((int)*remain)) + remain++; + mom->string = strdup(remain); + if(!mom->string) fatal_exit("out of memory"); + if(strlen(mom->string)>0) + mom->string[strlen(mom->string)-1]=0; + remain += strlen(mom->string); + } + } else if(parse_keyword(&remain, "CHECK_AUTOTRUST")) { + mom->evt_type = repevt_autotrust_check; + while(isspace((int)*remain)) + remain++; + if(strlen(remain)>0 && remain[strlen(remain)-1]=='\n') + remain[strlen(remain)-1] = 0; + mom->autotrust_id = strdup(remain); + if(!mom->autotrust_id) fatal_exit("out of memory"); + read_file_content(in, &pstate->lineno, mom); + } else if(parse_keyword(&remain, "ERROR")) { + mom->evt_type = repevt_error; + } else if(parse_keyword(&remain, "TRAFFIC")) { + mom->evt_type = repevt_traffic; + } else if(parse_keyword(&remain, "ASSIGN")) { + mom->evt_type = repevt_assign; + read_assign_step(remain, mom); + } else if(parse_keyword(&remain, "INFRA_RTT")) { + char *s, *m; + mom->evt_type = repevt_infra_rtt; + while(isspace((int)*remain)) + remain++; + s = remain; + remain = strchr(s, ' '); + if(!remain) fatal_exit("expected three args for INFRA_RTT"); + remain[0] = 0; + remain++; + while(isspace((int)*remain)) + remain++; + m = strchr(remain, ' '); + if(!m) fatal_exit("expected three args for INFRA_RTT"); + m[0] = 0; + m++; + while(isspace((int)*m)) + m++; + if(!extstrtoaddr(s, &mom->addr, &mom->addrlen)) + fatal_exit("bad infra_rtt address %s", s); + if(strlen(m)>0 && m[strlen(m)-1]=='\n') + m[strlen(m)-1] = 0; + mom->variable = strdup(remain); + mom->string = strdup(m); + if(!mom->string) fatal_exit("out of memory"); + if(!mom->variable) fatal_exit("out of memory"); + } else { + log_err("%d: unknown event type %s", pstate->lineno, remain); + free(mom); + return NULL; + } + while(isspace((int)*remain)) + remain++; + if(parse_keyword(&remain, "ADDRESS")) { + while(isspace((int)*remain)) + remain++; + if(strlen(remain) > 0) /* remove \n */ + remain[strlen(remain)-1] = 0; + if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen)) { + log_err("line %d: could not parse ADDRESS: %s", + pstate->lineno, remain); + free(mom); + return NULL; + } + } + if(parse_keyword(&remain, "ELAPSE")) { + double sec; + errno = 0; + sec = strtod(remain, &remain); + if(sec == 0. && errno != 0) { + log_err("line %d: could not parse ELAPSE: %s (%s)", + pstate->lineno, remain, strerror(errno)); + free(mom); + return NULL; + } +#ifndef S_SPLINT_S + mom->elapse.tv_sec = (int)sec; + mom->elapse.tv_usec = (int)((sec - (double)mom->elapse.tv_sec) + *1000000. + 0.5); +#endif + } + + if(readentry) { + mom->match = read_entry(in, name, pstate, 1); + if(!mom->match) { + free(mom); + return NULL; + } + } + + return mom; +} + +/** makes scenario with title on rest of line */ +static struct replay_scenario* +make_scenario(char* line) +{ + struct replay_scenario* scen; + while(isspace((int)*line)) + line++; + if(!*line) { + log_err("scenario: no title given"); + return NULL; + } + scen = (struct replay_scenario*)malloc(sizeof(struct replay_scenario)); + if(!scen) + return NULL; + memset(scen, 0, sizeof(*scen)); + scen->title = strdup(line); + if(!scen->title) { + free(scen); + return NULL; + } + return scen; +} + +struct replay_scenario* +replay_scenario_read(FILE* in, const char* name, int* lineno) +{ + char line[MAX_LINE_LEN]; + char *parse; + struct replay_scenario* scen = NULL; + struct sldns_file_parse_state pstate; + line[MAX_LINE_LEN-1]=0; + memset(&pstate, 0, sizeof(pstate)); + pstate.default_ttl = 3600; + pstate.lineno = *lineno; + + while(fgets(line, MAX_LINE_LEN-1, in)) { + parse=line; + pstate.lineno++; + (*lineno)++; + while(isspace((int)*parse)) + parse++; + if(!*parse) + continue; /* empty line */ + if(parse_keyword(&parse, ";")) + continue; /* comment */ + if(parse_keyword(&parse, "SCENARIO_BEGIN")) { + scen = make_scenario(parse); + if(!scen) + fatal_exit("%d: could not make scen", *lineno); + continue; + } + if(!scen) + fatal_exit("%d: expected SCENARIO", *lineno); + if(parse_keyword(&parse, "RANGE_BEGIN")) { + struct replay_range* newr = replay_range_read(parse, + in, name, &pstate, line); + if(!newr) + fatal_exit("%d: bad range", pstate.lineno); + *lineno = pstate.lineno; + newr->next_range = scen->range_list; + scen->range_list = newr; + } else if(parse_keyword(&parse, "STEP")) { + struct replay_moment* mom = replay_moment_read(parse, + in, name, &pstate); + if(!mom) + fatal_exit("%d: bad moment", pstate.lineno); + *lineno = pstate.lineno; + if(scen->mom_last && + scen->mom_last->time_step >= mom->time_step) + fatal_exit("%d: time goes backwards", *lineno); + if(scen->mom_last) + scen->mom_last->mom_next = mom; + else scen->mom_first = mom; + scen->mom_last = mom; + } else if(parse_keyword(&parse, "SCENARIO_END")) { + struct replay_moment *p = scen->mom_first; + int num = 0; + while(p) { + num++; + p = p->mom_next; + } + log_info("Scenario has %d steps", num); + return scen; + } + } + replay_scenario_delete(scen); + return NULL; +} + +void +replay_scenario_delete(struct replay_scenario* scen) +{ + struct replay_moment* mom, *momn; + struct replay_range* rng, *rngn; + if(!scen) + return; + if(scen->title) + free(scen->title); + mom = scen->mom_first; + while(mom) { + momn = mom->mom_next; + replay_moment_delete(mom); + mom = momn; + } + rng = scen->range_list; + while(rng) { + rngn = rng->next_range; + replay_range_delete(rng); + rng = rngn; + } + free(scen); +} + +/** fetch oldest timer in list that is enabled */ +static struct fake_timer* +first_timer(struct replay_runtime* runtime) +{ + struct fake_timer* p, *res = NULL; + for(p=runtime->timer_list; p; p=p->next) { + if(!p->enabled) + continue; + if(!res) + res = p; + else if(timeval_smaller(&p->tv, &res->tv)) + res = p; + } + return res; +} + +struct fake_timer* +replay_get_oldest_timer(struct replay_runtime* runtime) +{ + struct fake_timer* t = first_timer(runtime); + if(t && timeval_smaller(&t->tv, &runtime->now_tv)) + return t; + return NULL; +} + +int +replay_var_compare(const void* a, const void* b) +{ + struct replay_var* x = (struct replay_var*)a; + struct replay_var* y = (struct replay_var*)b; + return strcmp(x->name, y->name); +} + +rbtree_t* +macro_store_create(void) +{ + return rbtree_create(&replay_var_compare); +} + +/** helper function to delete macro values */ +static void +del_macro(rbnode_t* x, void* ATTR_UNUSED(arg)) +{ + struct replay_var* v = (struct replay_var*)x; + free(v->name); + free(v->value); + free(v); +} + +void +macro_store_delete(rbtree_t* store) +{ + if(!store) + return; + traverse_postorder(store, del_macro, NULL); + free(store); +} + +/** return length of macro */ +static size_t +macro_length(char* text) +{ + /* we are after ${, looking for } */ + int depth = 0; + size_t len = 0; + while(*text) { + len++; + if(*text == '}') { + if(depth == 0) + break; + depth--; + } else if(text[0] == '$' && text[1] == '{') { + depth++; + } + text++; + } + return len; +} + +/** insert new stuff at start of buffer */ +static int +do_buf_insert(char* buf, size_t remain, char* after, char* inserted) +{ + char* save = strdup(after); + size_t len; + if(!save) return 0; + if(strlen(inserted) > remain) { + free(save); + return 0; + } + len = strlcpy(buf, inserted, remain); + buf += len; + remain -= len; + (void)strlcpy(buf, save, remain); + free(save); + return 1; +} + +/** do macro recursion */ +static char* +do_macro_recursion(rbtree_t* store, struct replay_runtime* runtime, + char* at, size_t remain) +{ + char* after = at+2; + char* expand = macro_expand(store, runtime, &after); + if(!expand) + return NULL; /* expansion failed */ + if(!do_buf_insert(at, remain, after, expand)) { + free(expand); + return NULL; + } + free(expand); + return at; /* and parse over the expanded text to see if again */ +} + +/** get var from store */ +static struct replay_var* +macro_getvar(rbtree_t* store, char* name) +{ + struct replay_var k; + k.node.key = &k; + k.name = name; + return (struct replay_var*)rbtree_search(store, &k); +} + +/** do macro variable */ +static char* +do_macro_variable(rbtree_t* store, char* buf, size_t remain) +{ + struct replay_var* v; + char* at = buf+1; + char* name = at; + char sv; + if(at[0]==0) + return NULL; /* no variable name after $ */ + while(*at && (isalnum((int)*at) || *at=='_')) { + at++; + } + /* terminator, we are working in macro_expand() buffer */ + sv = *at; + *at = 0; + v = macro_getvar(store, name); + *at = sv; + + if(!v) { + log_err("variable is not defined: $%s", name); + return NULL; /* variable undefined is error for now */ + } + + /* insert the variable contents */ + if(!do_buf_insert(buf, remain, at, v->value)) + return NULL; + return buf; /* and expand the variable contents */ +} + +/** do ctime macro on argument */ +static char* +do_macro_ctime(char* arg) +{ + char buf[32]; + time_t tt = (time_t)atoi(arg); + if(tt == 0 && strcmp(arg, "0") != 0) { + log_err("macro ctime: expected number, not: %s", arg); + return NULL; + } + ctime_r(&tt, buf); + if(buf[0]) buf[strlen(buf)-1]=0; /* remove trailing newline */ + return strdup(buf); +} + +/** perform arithmetic operator */ +static double +perform_arith(double x, char op, double y, double* res) +{ + switch(op) { + case '+': + *res = x+y; + break; + case '-': + *res = x-y; + break; + case '/': + *res = x/y; + break; + case '*': + *res = x*y; + break; + default: + return 0; + } + + return 1; +} + +/** do macro arithmetic on two numbers and operand */ +static char* +do_macro_arith(char* orig, size_t remain, char** arithstart) +{ + double x, y, result; + char operator; + int skip; + char buf[32]; + char* at; + /* not yet done? we want number operand number expanded first. */ + if(!*arithstart) { + /* remember start pos of expr, skip the first number */ + at = orig; + *arithstart = at; + while(*at && (isdigit((int)*at) || *at == '.')) + at++; + return at; + } + /* move back to start */ + remain += (size_t)(orig - *arithstart); + at = *arithstart; + + /* parse operands */ + if(sscanf(at, " %lf %c %lf%n", &x, &operator, &y, &skip) != 3) { + *arithstart = NULL; + return do_macro_arith(orig, remain, arithstart); + } + if(isdigit((int)operator)) { + *arithstart = orig; + return at+skip; /* do nothing, but setup for later number */ + } + + /* calculate result */ + if(!perform_arith(x, operator, y, &result)) { + log_err("unknown operator: %s", at); + return NULL; + } + + /* put result back in buffer */ + snprintf(buf, sizeof(buf), "%.12g", result); + if(!do_buf_insert(at, remain, at+skip, buf)) + return NULL; + + /* the result can be part of another expression, restart that */ + *arithstart = NULL; + return at; +} + +/** Do range macro on expanded buffer */ +static char* +do_macro_range(char* buf) +{ + double x, y, z; + if(sscanf(buf, " %lf %lf %lf", &x, &y, &z) != 3) { + log_err("range func requires 3 args: %s", buf); + return NULL; + } + if(x <= y && y <= z) { + char res[1024]; + snprintf(res, sizeof(res), "%.24g", y); + return strdup(res); + } + fatal_exit("value %.24g not in range [%.24g, %.24g]", y, x, z); + return NULL; +} + +static char* +macro_expand(rbtree_t* store, struct replay_runtime* runtime, char** text) +{ + char buf[10240]; + char* at = *text; + size_t len = macro_length(at); + int dofunc = 0; + char* arithstart = NULL; + if(len >= sizeof(buf)) + return NULL; /* too long */ + buf[0] = 0; + (void)strlcpy(buf, at, len+1-1); /* do not copy last '}' character */ + at = buf; + + /* check for functions */ + if(strcmp(buf, "time") == 0) { + snprintf(buf, sizeof(buf), ARG_LL "d", (long long)runtime->now_secs); + *text += len; + return strdup(buf); + } else if(strcmp(buf, "timeout") == 0) { + time_t res = 0; + struct fake_timer* t = first_timer(runtime); + if(t && (time_t)t->tv.tv_sec >= runtime->now_secs) + res = (time_t)t->tv.tv_sec - runtime->now_secs; + snprintf(buf, sizeof(buf), ARG_LL "d", (long long)res); + *text += len; + return strdup(buf); + } else if(strncmp(buf, "ctime ", 6) == 0 || + strncmp(buf, "ctime\t", 6) == 0) { + at += 6; + dofunc = 1; + } else if(strncmp(buf, "range ", 6) == 0 || + strncmp(buf, "range\t", 6) == 0) { + at += 6; + dofunc = 1; + } + + /* actual macro text expansion */ + while(*at) { + size_t remain = sizeof(buf)-strlen(buf); + if(strncmp(at, "${", 2) == 0) { + at = do_macro_recursion(store, runtime, at, remain); + } else if(*at == '$') { + at = do_macro_variable(store, at, remain); + } else if(isdigit((int)*at)) { + at = do_macro_arith(at, remain, &arithstart); + } else { + /* copy until whitespace or operator */ + if(*at && (isalnum((int)*at) || *at=='_')) { + at++; + while(*at && (isalnum((int)*at) || *at=='_')) + at++; + } else at++; + } + if(!at) return NULL; /* failure */ + } + *text += len; + if(dofunc) { + /* post process functions, buf has the argument(s) */ + if(strncmp(buf, "ctime", 5) == 0) { + return do_macro_ctime(buf+6); + } else if(strncmp(buf, "range", 5) == 0) { + return do_macro_range(buf+6); + } + } + return strdup(buf); +} + +char* +macro_process(rbtree_t* store, struct replay_runtime* runtime, char* text) +{ + char buf[10240]; + char* next, *expand; + char* at = text; + if(!strstr(text, "${")) + return strdup(text); /* no macros */ + buf[0] = 0; + buf[sizeof(buf)-1]=0; + while( (next=strstr(at, "${")) ) { + /* copy text before next macro */ + if((size_t)(next-at) >= sizeof(buf)-strlen(buf)) + return NULL; /* string too long */ + (void)strlcpy(buf+strlen(buf), at, (size_t)(next-at+1)); + /* process the macro itself */ + next += 2; + expand = macro_expand(store, runtime, &next); + if(!expand) return NULL; /* expansion failed */ + (void)strlcpy(buf+strlen(buf), expand, sizeof(buf)-strlen(buf)); + free(expand); + at = next; + } + /* copy remainder fixed text */ + (void)strlcpy(buf+strlen(buf), at, sizeof(buf)-strlen(buf)); + return strdup(buf); +} + +char* +macro_lookup(rbtree_t* store, char* name) +{ + struct replay_var* x = macro_getvar(store, name); + if(!x) return strdup(""); + return strdup(x->value); +} + +void macro_print_debug(rbtree_t* store) +{ + struct replay_var* x; + RBTREE_FOR(x, struct replay_var*, store) { + log_info("%s = %s", x->name, x->value); + } +} + +int +macro_assign(rbtree_t* store, char* name, char* value) +{ + struct replay_var* x = macro_getvar(store, name); + if(x) { + free(x->value); + } else { + x = (struct replay_var*)malloc(sizeof(*x)); + if(!x) return 0; + x->node.key = x; + x->name = strdup(name); + if(!x->name) { + free(x); + return 0; + } + (void)rbtree_insert(store, &x->node); + } + x->value = strdup(value); + return x->value != NULL; +} + +void testbound_selftest(void) +{ + /* test the macro store */ + rbtree_t* store = macro_store_create(); + char* v; + int r; + log_assert(store); + + v = macro_lookup(store, "bla"); + log_assert(strcmp(v, "") == 0); + free(v); + + v = macro_lookup(store, "vlerk"); + log_assert(strcmp(v, "") == 0); + free(v); + + r = macro_assign(store, "bla", "waarde1"); + log_assert(r); + + v = macro_lookup(store, "vlerk"); + log_assert(strcmp(v, "") == 0); + free(v); + + v = macro_lookup(store, "bla"); + log_assert(strcmp(v, "waarde1") == 0); + free(v); + + r = macro_assign(store, "vlerk", "kanteel"); + log_assert(r); + + v = macro_lookup(store, "bla"); + log_assert(strcmp(v, "waarde1") == 0); + free(v); + + v = macro_lookup(store, "vlerk"); + log_assert(strcmp(v, "kanteel") == 0); + free(v); + + r = macro_assign(store, "bla", "ww"); + log_assert(r); + + v = macro_lookup(store, "bla"); + log_assert(strcmp(v, "ww") == 0); + free(v); + + log_assert( macro_length("}") == 1); + log_assert( macro_length("blabla}") == 7); + log_assert( macro_length("bla${zoink}bla}") == 7+8); + log_assert( macro_length("bla${zoink}${bla}bla}") == 7+8+6); + + v = macro_process(store, NULL, ""); + log_assert( v && strcmp(v, "") == 0); + free(v); + + v = macro_process(store, NULL, "${}"); + log_assert( v && strcmp(v, "") == 0); + free(v); + + v = macro_process(store, NULL, "blabla ${} dinges"); + log_assert( v && strcmp(v, "blabla dinges") == 0); + free(v); + + v = macro_process(store, NULL, "1${$bla}2${$bla}3"); + log_assert( v && strcmp(v, "1ww2ww3") == 0); + free(v); + + v = macro_process(store, NULL, "it is ${ctime 123456}"); + log_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); + free(v); + + r = macro_assign(store, "t1", "123456"); + log_assert(r); + v = macro_process(store, NULL, "it is ${ctime ${$t1}}"); + log_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); + free(v); + + v = macro_process(store, NULL, "it is ${ctime $t1}"); + log_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0); + free(v); + + r = macro_assign(store, "x", "1"); + log_assert(r); + r = macro_assign(store, "y", "2"); + log_assert(r); + v = macro_process(store, NULL, "${$x + $x}"); + log_assert( v && strcmp(v, "2") == 0); + free(v); + v = macro_process(store, NULL, "${$x - $x}"); + log_assert( v && strcmp(v, "0") == 0); + free(v); + v = macro_process(store, NULL, "${$y * $y}"); + log_assert( v && strcmp(v, "4") == 0); + free(v); + v = macro_process(store, NULL, "${32 / $y + $x + $y}"); + log_assert( v && strcmp(v, "19") == 0); + free(v); + + v = macro_process(store, NULL, "${32 / ${$y+$y} + ${${100*3}/3}}"); + log_assert( v && strcmp(v, "108") == 0); + free(v); + + v = macro_process(store, NULL, "${1 2 33 2 1}"); + log_assert( v && strcmp(v, "1 2 33 2 1") == 0); + free(v); + + v = macro_process(store, NULL, "${123 3 + 5}"); + log_assert( v && strcmp(v, "123 8") == 0); + free(v); + + v = macro_process(store, NULL, "${123 glug 3 + 5}"); + log_assert( v && strcmp(v, "123 glug 8") == 0); + free(v); + + macro_store_delete(store); +} |