xref: /openbsd/usr.sbin/unbound/testcode/replay.c (revision e5dd7070)
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  */
66 static char* macro_expand(rbtree_type* store,
67 	struct replay_runtime* runtime, char** text);
68 
69 /** compare of time values */
70 static int
71 timeval_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  */
90 static int
91 parse_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 */
102 static void
103 replay_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 */
118 static void
119 replay_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 */
128 static void
129 strip_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  */
150 static struct replay_range*
151 replay_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 */
214 static void
215 read_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 */
239 static void
240 read_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  */
267 static struct replay_moment*
268 replay_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 */
416 static struct replay_scenario*
417 make_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 
438 struct replay_scenario*
439 replay_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 
507 void
508 replay_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 */
531 static struct fake_timer*
532 first_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 
546 struct fake_timer*
547 replay_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 
555 int
556 replay_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 
563 rbtree_type*
564 macro_store_create(void)
565 {
566 	return rbtree_create(&replay_var_compare);
567 }
568 
569 /** helper function to delete macro values */
570 static void
571 del_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 
579 void
580 macro_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 */
589 static size_t
590 macro_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 */
610 static int
611 do_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 */
629 static char*
630 do_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 */
646 static struct replay_var*
647 macro_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 */
656 static char*
657 do_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 */
686 static char*
687 do_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 */
701 static double
702 perform_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 */
726 static char*
727 do_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 */
774 static char*
775 do_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 
791 static char*
792 macro_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 
864 char*
865 macro_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 
892 char*
893 macro_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 
900 void 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 
908 int
909 macro_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 
936 void 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