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