Home | History | Annotate | Line # | Download | only in testcode
      1      1.1  christos /*
      2      1.1  christos  * testcode/replay.c - store and use a replay of events for the DNS resolver.
      3      1.1  christos  *
      4      1.1  christos  * Copyright (c) 2007, NLnet Labs. All rights reserved.
      5  1.1.1.6  christos  *
      6      1.1  christos  * This software is open source.
      7  1.1.1.6  christos  *
      8      1.1  christos  * Redistribution and use in source and binary forms, with or without
      9      1.1  christos  * modification, are permitted provided that the following conditions
     10      1.1  christos  * are met:
     11  1.1.1.6  christos  *
     12      1.1  christos  * Redistributions of source code must retain the above copyright notice,
     13      1.1  christos  * this list of conditions and the following disclaimer.
     14  1.1.1.6  christos  *
     15      1.1  christos  * Redistributions in binary form must reproduce the above copyright notice,
     16      1.1  christos  * this list of conditions and the following disclaimer in the documentation
     17      1.1  christos  * and/or other materials provided with the distribution.
     18  1.1.1.6  christos  *
     19      1.1  christos  * Neither the name of the NLNET LABS nor the names of its contributors may
     20      1.1  christos  * be used to endorse or promote products derived from this software without
     21      1.1  christos  * specific prior written permission.
     22  1.1.1.6  christos  *
     23      1.1  christos  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     24      1.1  christos  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     25      1.1  christos  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     26      1.1  christos  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     27      1.1  christos  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     28      1.1  christos  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
     29      1.1  christos  * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     30      1.1  christos  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     31      1.1  christos  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     32      1.1  christos  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     33      1.1  christos  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     34      1.1  christos  */
     35      1.1  christos 
     36      1.1  christos /**
     37      1.1  christos  * \file
     38      1.1  christos  * Store and use a replay of events for the DNS resolver.
     39      1.1  christos  * Used to test known scenarios to get known outcomes.
     40      1.1  christos  */
     41      1.1  christos 
     42      1.1  christos #include "config.h"
     43      1.1  christos /* for strtod prototype */
     44      1.1  christos #include <math.h>
     45      1.1  christos #include <ctype.h>
     46      1.1  christos #include <time.h>
     47      1.1  christos #include "util/log.h"
     48      1.1  christos #include "util/net_help.h"
     49      1.1  christos #include "util/config_file.h"
     50      1.1  christos #include "testcode/replay.h"
     51      1.1  christos #include "testcode/testpkts.h"
     52      1.1  christos #include "testcode/fake_event.h"
     53      1.1  christos #include "sldns/str2wire.h"
     54  1.1.1.6  christos #include "util/timeval_func.h"
     55      1.1  christos 
     56      1.1  christos /** max length of lines in file */
     57      1.1  christos #define MAX_LINE_LEN 10240
     58      1.1  christos 
     59      1.1  christos /**
     60      1.1  christos  * Expand a macro
     61      1.1  christos  * @param store: value storage
     62      1.1  christos  * @param runtime: replay runtime for other stuff.
     63  1.1.1.6  christos  * @param text: the macro text, after the ${, Updated to after the } when
     64      1.1  christos  * 	done (successfully).
     65      1.1  christos  * @return expanded text, malloced. NULL on failure.
     66      1.1  christos  */
     67  1.1.1.6  christos static char* macro_expand(rbtree_type* store,
     68      1.1  christos 	struct replay_runtime* runtime, char** text);
     69      1.1  christos 
     70  1.1.1.6  christos /** parse keyword in string.
     71      1.1  christos  * @param line: if found, the line is advanced to after the keyword.
     72      1.1  christos  * @param keyword: string.
     73  1.1.1.6  christos  * @return: true if found, false if not.
     74      1.1  christos  */
     75  1.1.1.6  christos static int
     76      1.1  christos parse_keyword(char** line, const char* keyword)
     77      1.1  christos {
     78      1.1  christos 	size_t len = (size_t)strlen(keyword);
     79      1.1  christos 	if(strncmp(*line, keyword, len) == 0) {
     80      1.1  christos 		*line += len;
     81      1.1  christos 		return 1;
     82      1.1  christos 	}
     83      1.1  christos 	return 0;
     84      1.1  christos }
     85      1.1  christos 
     86      1.1  christos /** delete moment */
     87      1.1  christos static void
     88      1.1  christos replay_moment_delete(struct replay_moment* mom)
     89      1.1  christos {
     90      1.1  christos 	if(!mom)
     91      1.1  christos 		return;
     92      1.1  christos 	if(mom->match) {
     93      1.1  christos 		delete_entry(mom->match);
     94      1.1  christos 	}
     95      1.1  christos 	free(mom->autotrust_id);
     96      1.1  christos 	free(mom->string);
     97      1.1  christos 	free(mom->variable);
     98      1.1  christos 	config_delstrlist(mom->file_content);
     99      1.1  christos 	free(mom);
    100      1.1  christos }
    101      1.1  christos 
    102      1.1  christos /** delete range */
    103      1.1  christos static void
    104      1.1  christos replay_range_delete(struct replay_range* rng)
    105      1.1  christos {
    106      1.1  christos 	if(!rng)
    107      1.1  christos 		return;
    108      1.1  christos 	delete_entry(rng->match);
    109      1.1  christos 	free(rng);
    110      1.1  christos }
    111      1.1  christos 
    112  1.1.1.5  christos void
    113      1.1  christos strip_end_white(char* p)
    114      1.1  christos {
    115      1.1  christos 	size_t i;
    116      1.1  christos 	for(i = strlen(p); i > 0; i--) {
    117      1.1  christos 		if(isspace((unsigned char)p[i-1]))
    118      1.1  christos 			p[i-1] = 0;
    119      1.1  christos 		else return;
    120      1.1  christos 	}
    121      1.1  christos }
    122      1.1  christos 
    123  1.1.1.6  christos /**
    124  1.1.1.6  christos  * Read a range from file.
    125      1.1  christos  * @param remain: Rest of line (after RANGE keyword).
    126      1.1  christos  * @param in: file to read from.
    127      1.1  christos  * @param name: name to print in errors.
    128      1.1  christos  * @param pstate: read state structure with
    129      1.1  christos  * 	with lineno : incremented as lines are read.
    130      1.1  christos  * 	ttl, origin, prev for readentry.
    131      1.1  christos  * @param line: line buffer.
    132      1.1  christos  * @return: range object to add to list, or NULL on error.
    133      1.1  christos  */
    134      1.1  christos static struct replay_range*
    135      1.1  christos replay_range_read(char* remain, FILE* in, const char* name,
    136      1.1  christos 	struct sldns_file_parse_state* pstate, char* line)
    137      1.1  christos {
    138      1.1  christos 	struct replay_range* rng = (struct replay_range*)malloc(
    139      1.1  christos 		sizeof(struct replay_range));
    140      1.1  christos 	off_t pos;
    141      1.1  christos 	char *parse;
    142      1.1  christos 	struct entry* entry, *last = NULL;
    143      1.1  christos 	if(!rng)
    144      1.1  christos 		return NULL;
    145      1.1  christos 	memset(rng, 0, sizeof(*rng));
    146      1.1  christos 	/* read time range */
    147      1.1  christos 	if(sscanf(remain, " %d %d", &rng->start_step, &rng->end_step)!=2) {
    148      1.1  christos 		log_err("Could not read time range: %s", line);
    149      1.1  christos 		free(rng);
    150      1.1  christos 		return NULL;
    151      1.1  christos 	}
    152      1.1  christos 	/* read entries */
    153      1.1  christos 	pos = ftello(in);
    154      1.1  christos 	while(fgets(line, MAX_LINE_LEN-1, in)) {
    155      1.1  christos 		pstate->lineno++;
    156      1.1  christos 		parse = line;
    157      1.1  christos 		while(isspace((unsigned char)*parse))
    158      1.1  christos 			parse++;
    159      1.1  christos 		if(!*parse || *parse == ';') {
    160      1.1  christos 			pos = ftello(in);
    161      1.1  christos 			continue;
    162      1.1  christos 		}
    163      1.1  christos 		if(parse_keyword(&parse, "ADDRESS")) {
    164      1.1  christos 			while(isspace((unsigned char)*parse))
    165      1.1  christos 				parse++;
    166      1.1  christos 			strip_end_white(parse);
    167  1.1.1.6  christos 			if(!extstrtoaddr(parse, &rng->addr, &rng->addrlen,
    168  1.1.1.6  christos 				UNBOUND_DNS_PORT)) {
    169  1.1.1.6  christos 				log_err("Line %d: could not read ADDRESS: %s",
    170      1.1  christos 					pstate->lineno, parse);
    171      1.1  christos 				free(rng);
    172      1.1  christos 				return NULL;
    173      1.1  christos 			}
    174      1.1  christos 			pos = ftello(in);
    175      1.1  christos 			continue;
    176      1.1  christos 		}
    177      1.1  christos 		if(parse_keyword(&parse, "RANGE_END")) {
    178      1.1  christos 			return rng;
    179      1.1  christos 		}
    180      1.1  christos 		/* set position before line; read entry */
    181      1.1  christos 		pstate->lineno--;
    182      1.1  christos 		fseeko(in, pos, SEEK_SET);
    183      1.1  christos 		entry = read_entry(in, name, pstate, 1);
    184      1.1  christos 		if(!entry)
    185      1.1  christos 			fatal_exit("%d: bad entry", pstate->lineno);
    186      1.1  christos 		entry->next = NULL;
    187      1.1  christos 		if(last)
    188      1.1  christos 			last->next = entry;
    189      1.1  christos 		else	rng->match = entry;
    190      1.1  christos 		last = entry;
    191      1.1  christos 
    192      1.1  christos 		pos = ftello(in);
    193      1.1  christos 	}
    194      1.1  christos 	replay_range_delete(rng);
    195      1.1  christos 	return NULL;
    196      1.1  christos }
    197      1.1  christos 
    198      1.1  christos /** Read FILE match content */
    199      1.1  christos static void
    200      1.1  christos read_file_content(FILE* in, int* lineno, struct replay_moment* mom)
    201      1.1  christos {
    202      1.1  christos 	char line[MAX_LINE_LEN];
    203      1.1  christos 	char* remain = line;
    204      1.1  christos 	struct config_strlist** last = &mom->file_content;
    205      1.1  christos 	line[MAX_LINE_LEN-1]=0;
    206      1.1  christos 	if(!fgets(line, MAX_LINE_LEN-1, in))
    207      1.1  christos 		fatal_exit("FILE_BEGIN expected at line %d", *lineno);
    208      1.1  christos 	if(!parse_keyword(&remain, "FILE_BEGIN"))
    209      1.1  christos 		fatal_exit("FILE_BEGIN expected at line %d", *lineno);
    210      1.1  christos 	while(fgets(line, MAX_LINE_LEN-1, in)) {
    211      1.1  christos 		(*lineno)++;
    212      1.1  christos 		if(strncmp(line, "FILE_END", 8) == 0) {
    213      1.1  christos 			return;
    214      1.1  christos 		}
    215  1.1.1.5  christos 		strip_end_white(line);
    216      1.1  christos 		if(!cfg_strlist_insert(last, strdup(line)))
    217      1.1  christos 			fatal_exit("malloc failure");
    218      1.1  christos 		last = &( (*last)->next );
    219      1.1  christos 	}
    220      1.1  christos 	fatal_exit("no FILE_END in input file");
    221      1.1  christos }
    222      1.1  christos 
    223      1.1  christos /** read assign step info */
    224      1.1  christos static void
    225      1.1  christos read_assign_step(char* remain, struct replay_moment* mom)
    226      1.1  christos {
    227      1.1  christos 	char buf[1024];
    228      1.1  christos 	char eq;
    229      1.1  christos 	int skip;
    230      1.1  christos 	buf[sizeof(buf)-1]=0;
    231      1.1  christos 	if(sscanf(remain, " %1023s %c %n", buf, &eq, &skip) != 2)
    232      1.1  christos 		fatal_exit("cannot parse assign: %s", remain);
    233      1.1  christos 	mom->variable = strdup(buf);
    234      1.1  christos 	if(eq != '=')
    235      1.1  christos 		fatal_exit("no '=' in assign: %s", remain);
    236      1.1  christos 	remain += skip;
    237  1.1.1.5  christos 	strip_end_white(remain);
    238      1.1  christos 	mom->string = strdup(remain);
    239      1.1  christos 	if(!mom->variable || !mom->string)
    240      1.1  christos 		fatal_exit("out of memory");
    241      1.1  christos }
    242      1.1  christos 
    243  1.1.1.6  christos /**
    244  1.1.1.6  christos  * Read a replay moment 'STEP' from file.
    245      1.1  christos  * @param remain: Rest of line (after STEP keyword).
    246      1.1  christos  * @param in: file to read from.
    247      1.1  christos  * @param name: name to print in errors.
    248      1.1  christos  * @param pstate: with lineno, ttl, origin, prev for parse state.
    249      1.1  christos  * 	lineno is incremented.
    250      1.1  christos  * @return: range object to add to list, or NULL on error.
    251      1.1  christos  */
    252      1.1  christos static struct replay_moment*
    253      1.1  christos replay_moment_read(char* remain, FILE* in, const char* name,
    254      1.1  christos 	struct sldns_file_parse_state* pstate)
    255      1.1  christos {
    256      1.1  christos 	struct replay_moment* mom = (struct replay_moment*)malloc(
    257      1.1  christos 		sizeof(struct replay_moment));
    258      1.1  christos 	int skip = 0;
    259      1.1  christos 	int readentry = 0;
    260      1.1  christos 	if(!mom)
    261      1.1  christos 		return NULL;
    262      1.1  christos 	memset(mom, 0, sizeof(*mom));
    263      1.1  christos 	if(sscanf(remain, " %d%n", &mom->time_step, &skip) != 1) {
    264      1.1  christos 		log_err("%d: cannot read number: %s", pstate->lineno, remain);
    265      1.1  christos 		free(mom);
    266      1.1  christos 		return NULL;
    267      1.1  christos 	}
    268      1.1  christos 	remain += skip;
    269      1.1  christos 	while(isspace((unsigned char)*remain))
    270      1.1  christos 		remain++;
    271      1.1  christos 	if(parse_keyword(&remain, "NOTHING")) {
    272      1.1  christos 		mom->evt_type = repevt_nothing;
    273      1.1  christos 	} else if(parse_keyword(&remain, "QUERY")) {
    274      1.1  christos 		mom->evt_type = repevt_front_query;
    275      1.1  christos 		readentry = 1;
    276  1.1.1.6  christos 		if(!extstrtoaddr("127.0.0.1", &mom->addr, &mom->addrlen,
    277  1.1.1.6  christos 			UNBOUND_DNS_PORT))
    278      1.1  christos 			fatal_exit("internal error");
    279      1.1  christos 	} else if(parse_keyword(&remain, "CHECK_ANSWER")) {
    280      1.1  christos 		mom->evt_type = repevt_front_reply;
    281      1.1  christos 		readentry = 1;
    282      1.1  christos 	} else if(parse_keyword(&remain, "CHECK_OUT_QUERY")) {
    283      1.1  christos 		mom->evt_type = repevt_back_query;
    284      1.1  christos 		readentry = 1;
    285      1.1  christos 	} else if(parse_keyword(&remain, "REPLY")) {
    286      1.1  christos 		mom->evt_type = repevt_back_reply;
    287      1.1  christos 		readentry = 1;
    288      1.1  christos 	} else if(parse_keyword(&remain, "TIMEOUT")) {
    289      1.1  christos 		mom->evt_type = repevt_timeout;
    290      1.1  christos 	} else if(parse_keyword(&remain, "TIME_PASSES")) {
    291      1.1  christos 		mom->evt_type = repevt_time_passes;
    292      1.1  christos 		while(isspace((unsigned char)*remain))
    293      1.1  christos 			remain++;
    294      1.1  christos 		if(parse_keyword(&remain, "EVAL")) {
    295      1.1  christos 			while(isspace((unsigned char)*remain))
    296      1.1  christos 				remain++;
    297      1.1  christos 			mom->string = strdup(remain);
    298      1.1  christos 			if(!mom->string) fatal_exit("out of memory");
    299      1.1  christos 			if(strlen(mom->string)>0)
    300      1.1  christos 				mom->string[strlen(mom->string)-1]=0;
    301      1.1  christos 			remain += strlen(mom->string);
    302      1.1  christos 		}
    303      1.1  christos 	} else if(parse_keyword(&remain, "CHECK_AUTOTRUST")) {
    304      1.1  christos 		mom->evt_type = repevt_autotrust_check;
    305      1.1  christos 		while(isspace((unsigned char)*remain))
    306      1.1  christos 			remain++;
    307  1.1.1.5  christos 		strip_end_white(remain);
    308      1.1  christos 		mom->autotrust_id = strdup(remain);
    309      1.1  christos 		if(!mom->autotrust_id) fatal_exit("out of memory");
    310      1.1  christos 		read_file_content(in, &pstate->lineno, mom);
    311  1.1.1.3  christos 	} else if(parse_keyword(&remain, "CHECK_TEMPFILE")) {
    312  1.1.1.3  christos 		mom->evt_type = repevt_tempfile_check;
    313  1.1.1.3  christos 		while(isspace((unsigned char)*remain))
    314  1.1.1.3  christos 			remain++;
    315  1.1.1.5  christos 		strip_end_white(remain);
    316  1.1.1.3  christos 		mom->autotrust_id = strdup(remain);
    317  1.1.1.3  christos 		if(!mom->autotrust_id) fatal_exit("out of memory");
    318  1.1.1.3  christos 		read_file_content(in, &pstate->lineno, mom);
    319      1.1  christos 	} else if(parse_keyword(&remain, "ERROR")) {
    320      1.1  christos 		mom->evt_type = repevt_error;
    321      1.1  christos 	} else if(parse_keyword(&remain, "TRAFFIC")) {
    322      1.1  christos 		mom->evt_type = repevt_traffic;
    323      1.1  christos 	} else if(parse_keyword(&remain, "ASSIGN")) {
    324      1.1  christos 		mom->evt_type = repevt_assign;
    325      1.1  christos 		read_assign_step(remain, mom);
    326      1.1  christos 	} else if(parse_keyword(&remain, "INFRA_RTT")) {
    327      1.1  christos 		char *s, *m;
    328      1.1  christos 		mom->evt_type = repevt_infra_rtt;
    329      1.1  christos 		while(isspace((unsigned char)*remain))
    330      1.1  christos 			remain++;
    331      1.1  christos 		s = remain;
    332      1.1  christos 		remain = strchr(s, ' ');
    333      1.1  christos 		if(!remain) fatal_exit("expected three args for INFRA_RTT");
    334      1.1  christos 		remain[0] = 0;
    335      1.1  christos 		remain++;
    336      1.1  christos 		while(isspace((unsigned char)*remain))
    337      1.1  christos 			remain++;
    338      1.1  christos 		m = strchr(remain, ' ');
    339      1.1  christos 		if(!m) fatal_exit("expected three args for INFRA_RTT");
    340      1.1  christos 		m[0] = 0;
    341      1.1  christos 		m++;
    342      1.1  christos 		while(isspace((unsigned char)*m))
    343      1.1  christos 			m++;
    344  1.1.1.6  christos 		if(!extstrtoaddr(s, &mom->addr, &mom->addrlen, UNBOUND_DNS_PORT))
    345      1.1  christos 			fatal_exit("bad infra_rtt address %s", s);
    346  1.1.1.5  christos 		strip_end_white(m);
    347      1.1  christos 		mom->variable = strdup(remain);
    348      1.1  christos 		mom->string = strdup(m);
    349      1.1  christos 		if(!mom->string) fatal_exit("out of memory");
    350      1.1  christos 		if(!mom->variable) fatal_exit("out of memory");
    351  1.1.1.7  christos 	} else if(parse_keyword(&remain, "FLUSH_MESSAGE")) {
    352  1.1.1.7  christos 		mom->evt_type = repevt_flush_message;
    353  1.1.1.7  christos 		while(isspace((unsigned char)*remain))
    354  1.1.1.7  christos 			remain++;
    355  1.1.1.7  christos 		strip_end_white(remain);
    356  1.1.1.7  christos 		mom->string = strdup(remain);
    357  1.1.1.7  christos 		if(!mom->string) fatal_exit("out of memory");
    358  1.1.1.7  christos 	} else if(parse_keyword(&remain, "EXPIRE_MESSAGE")) {
    359  1.1.1.7  christos 		mom->evt_type = repevt_expire_message;
    360  1.1.1.7  christos 		while(isspace((unsigned char)*remain))
    361  1.1.1.7  christos 			remain++;
    362  1.1.1.7  christos 		strip_end_white(remain);
    363  1.1.1.7  christos 		mom->string = strdup(remain);
    364  1.1.1.7  christos 		if(!mom->string) fatal_exit("out of memory");
    365      1.1  christos 	} else {
    366      1.1  christos 		log_err("%d: unknown event type %s", pstate->lineno, remain);
    367      1.1  christos 		free(mom);
    368      1.1  christos 		return NULL;
    369      1.1  christos 	}
    370      1.1  christos 	while(isspace((unsigned char)*remain))
    371      1.1  christos 		remain++;
    372      1.1  christos 	if(parse_keyword(&remain, "ADDRESS")) {
    373      1.1  christos 		while(isspace((unsigned char)*remain))
    374      1.1  christos 			remain++;
    375  1.1.1.5  christos 		strip_end_white(remain);
    376  1.1.1.6  christos 		if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen,
    377  1.1.1.6  christos 			UNBOUND_DNS_PORT)) {
    378  1.1.1.6  christos 			log_err("line %d: could not parse ADDRESS: %s",
    379      1.1  christos 				pstate->lineno, remain);
    380      1.1  christos 			free(mom);
    381      1.1  christos 			return NULL;
    382      1.1  christos 		}
    383  1.1.1.6  christos 	}
    384      1.1  christos 	if(parse_keyword(&remain, "ELAPSE")) {
    385      1.1  christos 		double sec;
    386      1.1  christos 		errno = 0;
    387      1.1  christos 		sec = strtod(remain, &remain);
    388      1.1  christos 		if(sec == 0. && errno != 0) {
    389  1.1.1.6  christos 			log_err("line %d: could not parse ELAPSE: %s (%s)",
    390      1.1  christos 				pstate->lineno, remain, strerror(errno));
    391      1.1  christos 			free(mom);
    392      1.1  christos 			return NULL;
    393      1.1  christos 		}
    394      1.1  christos #ifndef S_SPLINT_S
    395      1.1  christos 		mom->elapse.tv_sec = (int)sec;
    396      1.1  christos 		mom->elapse.tv_usec = (int)((sec - (double)mom->elapse.tv_sec)
    397      1.1  christos 			*1000000. + 0.5);
    398      1.1  christos #endif
    399  1.1.1.6  christos 	}
    400      1.1  christos 
    401      1.1  christos 	if(readentry) {
    402      1.1  christos 		mom->match = read_entry(in, name, pstate, 1);
    403      1.1  christos 		if(!mom->match) {
    404      1.1  christos 			free(mom);
    405      1.1  christos 			return NULL;
    406      1.1  christos 		}
    407      1.1  christos 	}
    408      1.1  christos 
    409      1.1  christos 	return mom;
    410      1.1  christos }
    411      1.1  christos 
    412      1.1  christos /** makes scenario with title on rest of line */
    413      1.1  christos static struct replay_scenario*
    414      1.1  christos make_scenario(char* line)
    415      1.1  christos {
    416      1.1  christos 	struct replay_scenario* scen;
    417      1.1  christos 	while(isspace((unsigned char)*line))
    418      1.1  christos 		line++;
    419      1.1  christos 	if(!*line) {
    420      1.1  christos 		log_err("scenario: no title given");
    421      1.1  christos 		return NULL;
    422      1.1  christos 	}
    423      1.1  christos 	scen = (struct replay_scenario*)malloc(sizeof(struct replay_scenario));
    424      1.1  christos 	if(!scen)
    425      1.1  christos 		return NULL;
    426      1.1  christos 	memset(scen, 0, sizeof(*scen));
    427      1.1  christos 	scen->title = strdup(line);
    428      1.1  christos 	if(!scen->title) {
    429      1.1  christos 		free(scen);
    430      1.1  christos 		return NULL;
    431      1.1  christos 	}
    432      1.1  christos 	return scen;
    433      1.1  christos }
    434      1.1  christos 
    435  1.1.1.6  christos struct replay_scenario*
    436      1.1  christos replay_scenario_read(FILE* in, const char* name, int* lineno)
    437      1.1  christos {
    438      1.1  christos 	char line[MAX_LINE_LEN];
    439      1.1  christos 	char *parse;
    440      1.1  christos 	struct replay_scenario* scen = NULL;
    441      1.1  christos 	struct sldns_file_parse_state pstate;
    442      1.1  christos 	line[MAX_LINE_LEN-1]=0;
    443      1.1  christos 	memset(&pstate, 0, sizeof(pstate));
    444      1.1  christos 	pstate.default_ttl = 3600;
    445      1.1  christos 	pstate.lineno = *lineno;
    446      1.1  christos 
    447      1.1  christos 	while(fgets(line, MAX_LINE_LEN-1, in)) {
    448      1.1  christos 		parse=line;
    449      1.1  christos 		pstate.lineno++;
    450      1.1  christos 		(*lineno)++;
    451      1.1  christos 		while(isspace((unsigned char)*parse))
    452      1.1  christos 			parse++;
    453  1.1.1.6  christos 		if(!*parse)
    454      1.1  christos 			continue; /* empty line */
    455      1.1  christos 		if(parse_keyword(&parse, ";"))
    456      1.1  christos 			continue; /* comment */
    457      1.1  christos 		if(parse_keyword(&parse, "SCENARIO_BEGIN")) {
    458  1.1.1.4  christos 			if(scen)
    459  1.1.1.4  christos 				fatal_exit("%d: double SCENARIO_BEGIN", *lineno);
    460      1.1  christos 			scen = make_scenario(parse);
    461      1.1  christos 			if(!scen)
    462      1.1  christos 				fatal_exit("%d: could not make scen", *lineno);
    463      1.1  christos 			continue;
    464  1.1.1.6  christos 		}
    465      1.1  christos 		if(!scen)
    466      1.1  christos 			fatal_exit("%d: expected SCENARIO", *lineno);
    467      1.1  christos 		if(parse_keyword(&parse, "RANGE_BEGIN")) {
    468  1.1.1.6  christos 			struct replay_range* newr = replay_range_read(parse,
    469      1.1  christos 				in, name, &pstate, line);
    470      1.1  christos 			if(!newr)
    471      1.1  christos 				fatal_exit("%d: bad range", pstate.lineno);
    472      1.1  christos 			*lineno = pstate.lineno;
    473      1.1  christos 			newr->next_range = scen->range_list;
    474      1.1  christos 			scen->range_list = newr;
    475      1.1  christos 		} else if(parse_keyword(&parse, "STEP")) {
    476  1.1.1.6  christos 			struct replay_moment* mom = replay_moment_read(parse,
    477      1.1  christos 				in, name, &pstate);
    478      1.1  christos 			if(!mom)
    479      1.1  christos 				fatal_exit("%d: bad moment", pstate.lineno);
    480      1.1  christos 			*lineno = pstate.lineno;
    481  1.1.1.6  christos 			if(scen->mom_last &&
    482      1.1  christos 				scen->mom_last->time_step >= mom->time_step)
    483      1.1  christos 				fatal_exit("%d: time goes backwards", *lineno);
    484      1.1  christos 			if(scen->mom_last)
    485      1.1  christos 				scen->mom_last->mom_next = mom;
    486      1.1  christos 			else	scen->mom_first = mom;
    487      1.1  christos 			scen->mom_last = mom;
    488      1.1  christos 		} else if(parse_keyword(&parse, "SCENARIO_END")) {
    489      1.1  christos 			struct replay_moment *p = scen->mom_first;
    490      1.1  christos 			int num = 0;
    491      1.1  christos 			while(p) {
    492      1.1  christos 				num++;
    493      1.1  christos 				p = p->mom_next;
    494      1.1  christos 			}
    495      1.1  christos 			log_info("Scenario has %d steps", num);
    496      1.1  christos 			return scen;
    497      1.1  christos 		}
    498      1.1  christos 	}
    499  1.1.1.2  christos 	log_err("scenario read failed at line %d (no SCENARIO_END?)", *lineno);
    500      1.1  christos 	replay_scenario_delete(scen);
    501      1.1  christos 	return NULL;
    502      1.1  christos }
    503      1.1  christos 
    504  1.1.1.6  christos void
    505      1.1  christos replay_scenario_delete(struct replay_scenario* scen)
    506      1.1  christos {
    507      1.1  christos 	struct replay_moment* mom, *momn;
    508      1.1  christos 	struct replay_range* rng, *rngn;
    509      1.1  christos 	if(!scen)
    510      1.1  christos 		return;
    511      1.1  christos 	free(scen->title);
    512      1.1  christos 	mom = scen->mom_first;
    513      1.1  christos 	while(mom) {
    514      1.1  christos 		momn = mom->mom_next;
    515      1.1  christos 		replay_moment_delete(mom);
    516      1.1  christos 		mom = momn;
    517      1.1  christos 	}
    518      1.1  christos 	rng = scen->range_list;
    519      1.1  christos 	while(rng) {
    520      1.1  christos 		rngn = rng->next_range;
    521      1.1  christos 		replay_range_delete(rng);
    522      1.1  christos 		rng = rngn;
    523      1.1  christos 	}
    524      1.1  christos 	free(scen);
    525      1.1  christos }
    526      1.1  christos 
    527      1.1  christos /** fetch oldest timer in list that is enabled */
    528      1.1  christos static struct fake_timer*
    529      1.1  christos first_timer(struct replay_runtime* runtime)
    530      1.1  christos {
    531      1.1  christos 	struct fake_timer* p, *res = NULL;
    532      1.1  christos 	for(p=runtime->timer_list; p; p=p->next) {
    533      1.1  christos 		if(!p->enabled)
    534      1.1  christos 			continue;
    535      1.1  christos 		if(!res)
    536      1.1  christos 			res = p;
    537      1.1  christos 		else if(timeval_smaller(&p->tv, &res->tv))
    538      1.1  christos 			res = p;
    539      1.1  christos 	}
    540      1.1  christos 	return res;
    541      1.1  christos }
    542      1.1  christos 
    543      1.1  christos struct fake_timer*
    544      1.1  christos replay_get_oldest_timer(struct replay_runtime* runtime)
    545      1.1  christos {
    546      1.1  christos 	struct fake_timer* t = first_timer(runtime);
    547      1.1  christos 	if(t && timeval_smaller(&t->tv, &runtime->now_tv))
    548      1.1  christos 		return t;
    549      1.1  christos 	return NULL;
    550      1.1  christos }
    551      1.1  christos 
    552      1.1  christos int
    553      1.1  christos replay_var_compare(const void* a, const void* b)
    554      1.1  christos {
    555      1.1  christos 	struct replay_var* x = (struct replay_var*)a;
    556      1.1  christos 	struct replay_var* y = (struct replay_var*)b;
    557      1.1  christos 	return strcmp(x->name, y->name);
    558      1.1  christos }
    559      1.1  christos 
    560  1.1.1.2  christos rbtree_type*
    561      1.1  christos macro_store_create(void)
    562      1.1  christos {
    563      1.1  christos 	return rbtree_create(&replay_var_compare);
    564      1.1  christos }
    565      1.1  christos 
    566      1.1  christos /** helper function to delete macro values */
    567      1.1  christos static void
    568  1.1.1.2  christos del_macro(rbnode_type* x, void* ATTR_UNUSED(arg))
    569      1.1  christos {
    570      1.1  christos 	struct replay_var* v = (struct replay_var*)x;
    571      1.1  christos 	free(v->name);
    572      1.1  christos 	free(v->value);
    573      1.1  christos 	free(v);
    574      1.1  christos }
    575      1.1  christos 
    576      1.1  christos void
    577  1.1.1.2  christos macro_store_delete(rbtree_type* store)
    578      1.1  christos {
    579      1.1  christos 	if(!store)
    580      1.1  christos 		return;
    581      1.1  christos 	traverse_postorder(store, del_macro, NULL);
    582      1.1  christos 	free(store);
    583      1.1  christos }
    584      1.1  christos 
    585      1.1  christos /** return length of macro */
    586      1.1  christos static size_t
    587      1.1  christos macro_length(char* text)
    588      1.1  christos {
    589      1.1  christos 	/* we are after ${, looking for } */
    590      1.1  christos 	int depth = 0;
    591      1.1  christos 	size_t len = 0;
    592      1.1  christos 	while(*text) {
    593      1.1  christos 		len++;
    594      1.1  christos 		if(*text == '}') {
    595      1.1  christos 			if(depth == 0)
    596      1.1  christos 				break;
    597      1.1  christos 			depth--;
    598      1.1  christos 		} else if(text[0] == '$' && text[1] == '{') {
    599      1.1  christos 			depth++;
    600      1.1  christos 		}
    601      1.1  christos 		text++;
    602      1.1  christos 	}
    603      1.1  christos 	return len;
    604      1.1  christos }
    605      1.1  christos 
    606      1.1  christos /** insert new stuff at start of buffer */
    607      1.1  christos static int
    608      1.1  christos do_buf_insert(char* buf, size_t remain, char* after, char* inserted)
    609      1.1  christos {
    610      1.1  christos 	char* save = strdup(after);
    611      1.1  christos 	size_t len;
    612      1.1  christos 	if(!save) return 0;
    613      1.1  christos 	if(strlen(inserted) > remain) {
    614      1.1  christos 		free(save);
    615      1.1  christos 		return 0;
    616      1.1  christos 	}
    617      1.1  christos 	len = strlcpy(buf, inserted, remain);
    618      1.1  christos 	buf += len;
    619      1.1  christos 	remain -= len;
    620      1.1  christos 	(void)strlcpy(buf, save, remain);
    621      1.1  christos 	free(save);
    622      1.1  christos 	return 1;
    623      1.1  christos }
    624      1.1  christos 
    625      1.1  christos /** do macro recursion */
    626      1.1  christos static char*
    627  1.1.1.2  christos do_macro_recursion(rbtree_type* store, struct replay_runtime* runtime,
    628      1.1  christos 	char* at, size_t remain)
    629      1.1  christos {
    630      1.1  christos 	char* after = at+2;
    631      1.1  christos 	char* expand = macro_expand(store, runtime, &after);
    632  1.1.1.6  christos 	if(!expand)
    633      1.1  christos 		return NULL; /* expansion failed */
    634      1.1  christos 	if(!do_buf_insert(at, remain, after, expand)) {
    635      1.1  christos 		free(expand);
    636      1.1  christos 		return NULL;
    637      1.1  christos 	}
    638      1.1  christos 	free(expand);
    639      1.1  christos 	return at; /* and parse over the expanded text to see if again */
    640      1.1  christos }
    641      1.1  christos 
    642      1.1  christos /** get var from store */
    643      1.1  christos static struct replay_var*
    644  1.1.1.2  christos macro_getvar(rbtree_type* store, char* name)
    645      1.1  christos {
    646      1.1  christos 	struct replay_var k;
    647      1.1  christos 	k.node.key = &k;
    648      1.1  christos 	k.name = name;
    649      1.1  christos 	return (struct replay_var*)rbtree_search(store, &k);
    650      1.1  christos }
    651      1.1  christos 
    652      1.1  christos /** do macro variable */
    653      1.1  christos static char*
    654  1.1.1.2  christos do_macro_variable(rbtree_type* store, char* buf, size_t remain)
    655      1.1  christos {
    656      1.1  christos 	struct replay_var* v;
    657      1.1  christos 	char* at = buf+1;
    658      1.1  christos 	char* name = at;
    659      1.1  christos 	char sv;
    660      1.1  christos 	if(at[0]==0)
    661      1.1  christos 		return NULL; /* no variable name after $ */
    662      1.1  christos 	while(*at && (isalnum((unsigned char)*at) || *at=='_')) {
    663      1.1  christos 		at++;
    664      1.1  christos 	}
    665      1.1  christos 	/* terminator, we are working in macro_expand() buffer */
    666      1.1  christos 	sv = *at;
    667  1.1.1.6  christos 	*at = 0;
    668      1.1  christos 	v = macro_getvar(store, name);
    669      1.1  christos 	*at = sv;
    670      1.1  christos 
    671      1.1  christos 	if(!v) {
    672      1.1  christos 		log_err("variable is not defined: $%s", name);
    673      1.1  christos 		return NULL; /* variable undefined is error for now */
    674      1.1  christos 	}
    675      1.1  christos 
    676      1.1  christos 	/* insert the variable contents */
    677      1.1  christos 	if(!do_buf_insert(buf, remain, at, v->value))
    678      1.1  christos 		return NULL;
    679      1.1  christos 	return buf; /* and expand the variable contents */
    680      1.1  christos }
    681      1.1  christos 
    682      1.1  christos /** do ctime macro on argument */
    683      1.1  christos static char*
    684      1.1  christos do_macro_ctime(char* arg)
    685      1.1  christos {
    686      1.1  christos 	char buf[32];
    687      1.1  christos 	time_t tt = (time_t)atoi(arg);
    688      1.1  christos 	if(tt == 0 && strcmp(arg, "0") != 0) {
    689      1.1  christos 		log_err("macro ctime: expected number, not: %s", arg);
    690      1.1  christos 		return NULL;
    691      1.1  christos 	}
    692      1.1  christos 	ctime_r(&tt, buf);
    693  1.1.1.5  christos #ifdef USE_WINSOCK
    694  1.1.1.5  christos 	if(strlen(buf) > 10 && buf[7]==' ' && buf[8]=='0')
    695  1.1.1.5  christos 		buf[8]=' '; /* fix error in windows ctime */
    696  1.1.1.5  christos #endif
    697  1.1.1.5  christos 	strip_end_white(buf);
    698      1.1  christos 	return strdup(buf);
    699      1.1  christos }
    700      1.1  christos 
    701      1.1  christos /** perform arithmetic operator */
    702      1.1  christos static double
    703      1.1  christos perform_arith(double x, char op, double y, double* res)
    704      1.1  christos {
    705      1.1  christos 	switch(op) {
    706      1.1  christos 	case '+':
    707      1.1  christos 		*res = x+y;
    708      1.1  christos 		break;
    709      1.1  christos 	case '-':
    710      1.1  christos 		*res = x-y;
    711      1.1  christos 		break;
    712      1.1  christos 	case '/':
    713      1.1  christos 		*res = x/y;
    714      1.1  christos 		break;
    715      1.1  christos 	case '*':
    716      1.1  christos 		*res = x*y;
    717      1.1  christos 		break;
    718      1.1  christos 	default:
    719  1.1.1.4  christos 		*res = 0;
    720      1.1  christos 		return 0;
    721      1.1  christos 	}
    722      1.1  christos 
    723      1.1  christos 	return 1;
    724      1.1  christos }
    725      1.1  christos 
    726      1.1  christos /** do macro arithmetic on two numbers and operand */
    727      1.1  christos static char*
    728      1.1  christos do_macro_arith(char* orig, size_t remain, char** arithstart)
    729      1.1  christos {
    730      1.1  christos 	double x, y, result;
    731      1.1  christos 	char operator;
    732      1.1  christos 	int skip;
    733      1.1  christos 	char buf[32];
    734      1.1  christos 	char* at;
    735      1.1  christos 	/* not yet done? we want number operand number expanded first. */
    736      1.1  christos 	if(!*arithstart) {
    737      1.1  christos 		/* remember start pos of expr, skip the first number */
    738      1.1  christos 		at = orig;
    739      1.1  christos 		*arithstart = at;
    740      1.1  christos 		while(*at && (isdigit((unsigned char)*at) || *at == '.'))
    741      1.1  christos 			at++;
    742      1.1  christos 		return at;
    743      1.1  christos 	}
    744      1.1  christos 	/* move back to start */
    745      1.1  christos 	remain += (size_t)(orig - *arithstart);
    746      1.1  christos 	at = *arithstart;
    747      1.1  christos 
    748      1.1  christos 	/* parse operands */
    749      1.1  christos 	if(sscanf(at, " %lf %c %lf%n", &x, &operator, &y, &skip) != 3) {
    750      1.1  christos 		*arithstart = NULL;
    751      1.1  christos 		return do_macro_arith(orig, remain, arithstart);
    752      1.1  christos 	}
    753      1.1  christos 	if(isdigit((unsigned char)operator)) {
    754      1.1  christos 		*arithstart = orig;
    755      1.1  christos 		return at+skip; /* do nothing, but setup for later number */
    756      1.1  christos 	}
    757      1.1  christos 
    758      1.1  christos 	/* calculate result */
    759      1.1  christos 	if(!perform_arith(x, operator, y, &result)) {
    760      1.1  christos 		log_err("unknown operator: %s", at);
    761      1.1  christos 		return NULL;
    762      1.1  christos 	}
    763      1.1  christos 
    764      1.1  christos 	/* put result back in buffer */
    765      1.1  christos 	snprintf(buf, sizeof(buf), "%.12g", result);
    766      1.1  christos 	if(!do_buf_insert(at, remain, at+skip, buf))
    767      1.1  christos 		return NULL;
    768      1.1  christos 
    769      1.1  christos 	/* the result can be part of another expression, restart that */
    770      1.1  christos 	*arithstart = NULL;
    771      1.1  christos 	return at;
    772      1.1  christos }
    773      1.1  christos 
    774      1.1  christos /** Do range macro on expanded buffer */
    775      1.1  christos static char*
    776      1.1  christos do_macro_range(char* buf)
    777      1.1  christos {
    778      1.1  christos 	double x, y, z;
    779      1.1  christos 	if(sscanf(buf, " %lf %lf %lf", &x, &y, &z) != 3) {
    780      1.1  christos 		log_err("range func requires 3 args: %s", buf);
    781      1.1  christos 		return NULL;
    782      1.1  christos 	}
    783      1.1  christos 	if(x <= y && y <= z) {
    784      1.1  christos 		char res[1024];
    785      1.1  christos 		snprintf(res, sizeof(res), "%.24g", y);
    786      1.1  christos 		return strdup(res);
    787      1.1  christos 	}
    788      1.1  christos 	fatal_exit("value %.24g not in range [%.24g, %.24g]", y, x, z);
    789      1.1  christos 	return NULL;
    790      1.1  christos }
    791      1.1  christos 
    792      1.1  christos static char*
    793  1.1.1.2  christos macro_expand(rbtree_type* store, struct replay_runtime* runtime, char** text)
    794      1.1  christos {
    795      1.1  christos 	char buf[10240];
    796      1.1  christos 	char* at = *text;
    797      1.1  christos 	size_t len = macro_length(at);
    798  1.1.1.8  christos 	int tries = 0, dofunc = 0;
    799      1.1  christos 	char* arithstart = NULL;
    800      1.1  christos 	if(len >= sizeof(buf))
    801      1.1  christos 		return NULL; /* too long */
    802      1.1  christos 	buf[0] = 0;
    803      1.1  christos 	(void)strlcpy(buf, at, len+1-1); /* do not copy last '}' character */
    804      1.1  christos 	at = buf;
    805      1.1  christos 
    806      1.1  christos 	/* check for functions */
    807      1.1  christos 	if(strcmp(buf, "time") == 0) {
    808  1.1.1.4  christos 		if(runtime)
    809  1.1.1.4  christos 			snprintf(buf, sizeof(buf), ARG_LL "d", (long long)runtime->now_secs);
    810  1.1.1.4  christos 		else
    811  1.1.1.4  christos 			snprintf(buf, sizeof(buf), ARG_LL "d", (long long)0);
    812      1.1  christos 		*text += len;
    813      1.1  christos 		return strdup(buf);
    814      1.1  christos 	} else if(strcmp(buf, "timeout") == 0) {
    815      1.1  christos 		time_t res = 0;
    816  1.1.1.4  christos 		if(runtime) {
    817  1.1.1.4  christos 			struct fake_timer* t = first_timer(runtime);
    818  1.1.1.6  christos 			if(t && (time_t)t->tv.tv_sec >= runtime->now_secs)
    819  1.1.1.4  christos 				res = (time_t)t->tv.tv_sec - runtime->now_secs;
    820  1.1.1.4  christos 		}
    821      1.1  christos 		snprintf(buf, sizeof(buf), ARG_LL "d", (long long)res);
    822      1.1  christos 		*text += len;
    823      1.1  christos 		return strdup(buf);
    824      1.1  christos 	} else if(strncmp(buf, "ctime ", 6) == 0 ||
    825      1.1  christos 		strncmp(buf, "ctime\t", 6) == 0) {
    826      1.1  christos 		at += 6;
    827      1.1  christos 		dofunc = 1;
    828      1.1  christos 	} else if(strncmp(buf, "range ", 6) == 0 ||
    829      1.1  christos 		strncmp(buf, "range\t", 6) == 0) {
    830      1.1  christos 		at += 6;
    831      1.1  christos 		dofunc = 1;
    832      1.1  christos 	}
    833      1.1  christos 
    834      1.1  christos 	/* actual macro text expansion */
    835      1.1  christos 	while(*at) {
    836      1.1  christos 		size_t remain = sizeof(buf)-strlen(buf);
    837  1.1.1.8  christos 		if(tries++ > 10000)
    838  1.1.1.8  christos 			return NULL; /* looks like got into an infinite loop, bail out */
    839      1.1  christos 		if(strncmp(at, "${", 2) == 0) {
    840      1.1  christos 			at = do_macro_recursion(store, runtime, at, remain);
    841      1.1  christos 		} else if(*at == '$') {
    842      1.1  christos 			at = do_macro_variable(store, at, remain);
    843      1.1  christos 		} else if(isdigit((unsigned char)*at)) {
    844      1.1  christos 			at = do_macro_arith(at, remain, &arithstart);
    845      1.1  christos 		} else {
    846      1.1  christos 			/* copy until whitespace or operator */
    847      1.1  christos 			if(*at && (isalnum((unsigned char)*at) || *at=='_')) {
    848      1.1  christos 				at++;
    849      1.1  christos 				while(*at && (isalnum((unsigned char)*at) || *at=='_'))
    850      1.1  christos 					at++;
    851      1.1  christos 			} else at++;
    852      1.1  christos 		}
    853      1.1  christos 		if(!at) return NULL; /* failure */
    854      1.1  christos 	}
    855      1.1  christos 	*text += len;
    856      1.1  christos 	if(dofunc) {
    857      1.1  christos 		/* post process functions, buf has the argument(s) */
    858      1.1  christos 		if(strncmp(buf, "ctime", 5) == 0) {
    859  1.1.1.6  christos 			return do_macro_ctime(buf+6);
    860      1.1  christos 		} else if(strncmp(buf, "range", 5) == 0) {
    861  1.1.1.6  christos 			return do_macro_range(buf+6);
    862      1.1  christos 		}
    863      1.1  christos 	}
    864      1.1  christos 	return strdup(buf);
    865      1.1  christos }
    866      1.1  christos 
    867      1.1  christos char*
    868  1.1.1.2  christos macro_process(rbtree_type* store, struct replay_runtime* runtime, char* text)
    869      1.1  christos {
    870      1.1  christos 	char buf[10240];
    871      1.1  christos 	char* next, *expand;
    872      1.1  christos 	char* at = text;
    873      1.1  christos 	if(!strstr(text, "${"))
    874      1.1  christos 		return strdup(text); /* no macros */
    875      1.1  christos 	buf[0] = 0;
    876      1.1  christos 	buf[sizeof(buf)-1]=0;
    877      1.1  christos 	while( (next=strstr(at, "${")) ) {
    878      1.1  christos 		/* copy text before next macro */
    879      1.1  christos 		if((size_t)(next-at) >= sizeof(buf)-strlen(buf))
    880      1.1  christos 			return NULL; /* string too long */
    881      1.1  christos 		(void)strlcpy(buf+strlen(buf), at, (size_t)(next-at+1));
    882      1.1  christos 		/* process the macro itself */
    883      1.1  christos 		next += 2;
    884      1.1  christos 		expand = macro_expand(store, runtime, &next);
    885      1.1  christos 		if(!expand) return NULL; /* expansion failed */
    886      1.1  christos 		(void)strlcpy(buf+strlen(buf), expand, sizeof(buf)-strlen(buf));
    887      1.1  christos 		free(expand);
    888      1.1  christos 		at = next;
    889      1.1  christos 	}
    890      1.1  christos 	/* copy remainder fixed text */
    891      1.1  christos 	(void)strlcpy(buf+strlen(buf), at, sizeof(buf)-strlen(buf));
    892      1.1  christos 	return strdup(buf);
    893      1.1  christos }
    894      1.1  christos 
    895  1.1.1.6  christos char*
    896  1.1.1.2  christos macro_lookup(rbtree_type* store, char* name)
    897      1.1  christos {
    898      1.1  christos 	struct replay_var* x = macro_getvar(store, name);
    899      1.1  christos 	if(!x) return strdup("");
    900      1.1  christos 	return strdup(x->value);
    901      1.1  christos }
    902      1.1  christos 
    903  1.1.1.2  christos void macro_print_debug(rbtree_type* store)
    904      1.1  christos {
    905      1.1  christos 	struct replay_var* x;
    906      1.1  christos 	RBTREE_FOR(x, struct replay_var*, store) {
    907      1.1  christos 		log_info("%s = %s", x->name, x->value);
    908      1.1  christos 	}
    909      1.1  christos }
    910      1.1  christos 
    911  1.1.1.6  christos int
    912  1.1.1.2  christos macro_assign(rbtree_type* store, char* name, char* value)
    913      1.1  christos {
    914      1.1  christos 	struct replay_var* x = macro_getvar(store, name);
    915      1.1  christos 	if(x) {
    916      1.1  christos 		free(x->value);
    917      1.1  christos 	} else {
    918      1.1  christos 		x = (struct replay_var*)malloc(sizeof(*x));
    919      1.1  christos 		if(!x) return 0;
    920      1.1  christos 		x->node.key = x;
    921      1.1  christos 		x->name = strdup(name);
    922      1.1  christos 		if(!x->name) {
    923      1.1  christos 			free(x);
    924      1.1  christos 			return 0;
    925      1.1  christos 		}
    926      1.1  christos 		(void)rbtree_insert(store, &x->node);
    927      1.1  christos 	}
    928      1.1  christos 	x->value = strdup(value);
    929      1.1  christos 	return x->value != NULL;
    930      1.1  christos }
    931      1.1  christos 
    932      1.1  christos /* testbound assert function for selftest.  counts the number of tests */
    933      1.1  christos #define tb_assert(x) \
    934      1.1  christos 	do { if(!(x)) fatal_exit("%s:%d: %s: assertion %s failed", \
    935      1.1  christos 		__FILE__, __LINE__, __func__, #x); \
    936      1.1  christos 		num_asserts++; \
    937      1.1  christos 	} while(0);
    938      1.1  christos 
    939      1.1  christos void testbound_selftest(void)
    940      1.1  christos {
    941      1.1  christos 	/* test the macro store */
    942  1.1.1.2  christos 	rbtree_type* store = macro_store_create();
    943      1.1  christos 	char* v;
    944      1.1  christos 	int r;
    945      1.1  christos 	int num_asserts = 0;
    946      1.1  christos 	tb_assert(store);
    947      1.1  christos 
    948      1.1  christos 	v = macro_lookup(store, "bla");
    949      1.1  christos 	tb_assert(strcmp(v, "") == 0);
    950      1.1  christos 	free(v);
    951      1.1  christos 
    952      1.1  christos 	v = macro_lookup(store, "vlerk");
    953      1.1  christos 	tb_assert(strcmp(v, "") == 0);
    954      1.1  christos 	free(v);
    955      1.1  christos 
    956      1.1  christos 	r = macro_assign(store, "bla", "waarde1");
    957      1.1  christos 	tb_assert(r);
    958      1.1  christos 
    959      1.1  christos 	v = macro_lookup(store, "vlerk");
    960      1.1  christos 	tb_assert(strcmp(v, "") == 0);
    961      1.1  christos 	free(v);
    962      1.1  christos 
    963      1.1  christos 	v = macro_lookup(store, "bla");
    964      1.1  christos 	tb_assert(strcmp(v, "waarde1") == 0);
    965      1.1  christos 	free(v);
    966      1.1  christos 
    967      1.1  christos 	r = macro_assign(store, "vlerk", "kanteel");
    968      1.1  christos 	tb_assert(r);
    969      1.1  christos 
    970      1.1  christos 	v = macro_lookup(store, "bla");
    971      1.1  christos 	tb_assert(strcmp(v, "waarde1") == 0);
    972      1.1  christos 	free(v);
    973      1.1  christos 
    974      1.1  christos 	v = macro_lookup(store, "vlerk");
    975      1.1  christos 	tb_assert(strcmp(v, "kanteel") == 0);
    976      1.1  christos 	free(v);
    977      1.1  christos 
    978      1.1  christos 	r = macro_assign(store, "bla", "ww");
    979      1.1  christos 	tb_assert(r);
    980      1.1  christos 
    981      1.1  christos 	v = macro_lookup(store, "bla");
    982      1.1  christos 	tb_assert(strcmp(v, "ww") == 0);
    983      1.1  christos 	free(v);
    984      1.1  christos 
    985      1.1  christos 	tb_assert( macro_length("}") == 1);
    986      1.1  christos 	tb_assert( macro_length("blabla}") == 7);
    987      1.1  christos 	tb_assert( macro_length("bla${zoink}bla}") == 7+8);
    988      1.1  christos 	tb_assert( macro_length("bla${zoink}${bla}bla}") == 7+8+6);
    989      1.1  christos 
    990      1.1  christos 	v = macro_process(store, NULL, "");
    991      1.1  christos 	tb_assert( v && strcmp(v, "") == 0);
    992      1.1  christos 	free(v);
    993      1.1  christos 
    994      1.1  christos 	v = macro_process(store, NULL, "${}");
    995      1.1  christos 	tb_assert( v && strcmp(v, "") == 0);
    996      1.1  christos 	free(v);
    997      1.1  christos 
    998      1.1  christos 	v = macro_process(store, NULL, "blabla ${} dinges");
    999      1.1  christos 	tb_assert( v && strcmp(v, "blabla  dinges") == 0);
   1000      1.1  christos 	free(v);
   1001      1.1  christos 
   1002      1.1  christos 	v = macro_process(store, NULL, "1${$bla}2${$bla}3");
   1003      1.1  christos 	tb_assert( v && strcmp(v, "1ww2ww3") == 0);
   1004      1.1  christos 	free(v);
   1005      1.1  christos 
   1006      1.1  christos 	v = macro_process(store, NULL, "it is ${ctime 123456}");
   1007      1.1  christos 	tb_assert( v && strcmp(v, "it is Fri Jan  2 10:17:36 1970") == 0);
   1008      1.1  christos 	free(v);
   1009      1.1  christos 
   1010      1.1  christos 	r = macro_assign(store, "t1", "123456");
   1011      1.1  christos 	tb_assert(r);
   1012      1.1  christos 	v = macro_process(store, NULL, "it is ${ctime ${$t1}}");
   1013      1.1  christos 	tb_assert( v && strcmp(v, "it is Fri Jan  2 10:17:36 1970") == 0);
   1014      1.1  christos 	free(v);
   1015      1.1  christos 
   1016      1.1  christos 	v = macro_process(store, NULL, "it is ${ctime $t1}");
   1017      1.1  christos 	tb_assert( v && strcmp(v, "it is Fri Jan  2 10:17:36 1970") == 0);
   1018      1.1  christos 	free(v);
   1019      1.1  christos 
   1020      1.1  christos 	r = macro_assign(store, "x", "1");
   1021      1.1  christos 	tb_assert(r);
   1022      1.1  christos 	r = macro_assign(store, "y", "2");
   1023      1.1  christos 	tb_assert(r);
   1024      1.1  christos 	v = macro_process(store, NULL, "${$x + $x}");
   1025      1.1  christos 	tb_assert( v && strcmp(v, "2") == 0);
   1026      1.1  christos 	free(v);
   1027      1.1  christos 	v = macro_process(store, NULL, "${$x - $x}");
   1028      1.1  christos 	tb_assert( v && strcmp(v, "0") == 0);
   1029      1.1  christos 	free(v);
   1030      1.1  christos 	v = macro_process(store, NULL, "${$y * $y}");
   1031      1.1  christos 	tb_assert( v && strcmp(v, "4") == 0);
   1032      1.1  christos 	free(v);
   1033      1.1  christos 	v = macro_process(store, NULL, "${32 / $y + $x + $y}");
   1034      1.1  christos 	tb_assert( v && strcmp(v, "19") == 0);
   1035      1.1  christos 	free(v);
   1036      1.1  christos 
   1037      1.1  christos 	v = macro_process(store, NULL, "${32 / ${$y+$y} + ${${100*3}/3}}");
   1038      1.1  christos 	tb_assert( v && strcmp(v, "108") == 0);
   1039      1.1  christos 	free(v);
   1040      1.1  christos 
   1041      1.1  christos 	v = macro_process(store, NULL, "${1 2 33 2 1}");
   1042      1.1  christos 	tb_assert( v && strcmp(v, "1 2 33 2 1") == 0);
   1043      1.1  christos 	free(v);
   1044      1.1  christos 
   1045      1.1  christos 	v = macro_process(store, NULL, "${123 3 + 5}");
   1046      1.1  christos 	tb_assert( v && strcmp(v, "123 8") == 0);
   1047      1.1  christos 	free(v);
   1048      1.1  christos 
   1049      1.1  christos 	v = macro_process(store, NULL, "${123 glug 3 + 5}");
   1050      1.1  christos 	tb_assert( v && strcmp(v, "123 glug 8") == 0);
   1051      1.1  christos 	free(v);
   1052      1.1  christos 
   1053      1.1  christos 	macro_store_delete(store);
   1054      1.1  christos 	printf("selftest successful (%d checks).\n", num_asserts);
   1055      1.1  christos }
   1056