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