xref: /netbsd-src/external/bsd/unbound/dist/testcode/replay.c (revision 91f7d55fb697b5e0475da4718fa34c3a3ebeac85)
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  */
67 static 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  */
75 static int
parse_keyword(char ** line,const char * keyword)76 parse_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 */
87 static void
replay_moment_delete(struct replay_moment * mom)88 replay_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 */
103 static void
replay_range_delete(struct replay_range * rng)104 replay_range_delete(struct replay_range* rng)
105 {
106 	if(!rng)
107 		return;
108 	delete_entry(rng->match);
109 	free(rng);
110 }
111 
112 void
strip_end_white(char * p)113 strip_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  */
134 static struct replay_range*
replay_range_read(char * remain,FILE * in,const char * name,struct sldns_file_parse_state * pstate,char * line)135 replay_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 */
199 static void
read_file_content(FILE * in,int * lineno,struct replay_moment * mom)200 read_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 */
224 static void
read_assign_step(char * remain,struct replay_moment * mom)225 read_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  */
252 static struct replay_moment*
replay_moment_read(char * remain,FILE * in,const char * name,struct sldns_file_parse_state * pstate)253 replay_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 */
399 static struct replay_scenario*
make_scenario(char * line)400 make_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 
421 struct replay_scenario*
replay_scenario_read(FILE * in,const char * name,int * lineno)422 replay_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 
490 void
replay_scenario_delete(struct replay_scenario * scen)491 replay_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 */
514 static struct fake_timer*
first_timer(struct replay_runtime * runtime)515 first_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 
529 struct fake_timer*
replay_get_oldest_timer(struct replay_runtime * runtime)530 replay_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 
538 int
replay_var_compare(const void * a,const void * b)539 replay_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 
546 rbtree_type*
macro_store_create(void)547 macro_store_create(void)
548 {
549 	return rbtree_create(&replay_var_compare);
550 }
551 
552 /** helper function to delete macro values */
553 static void
del_macro(rbnode_type * x,void * ATTR_UNUSED (arg))554 del_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 
562 void
macro_store_delete(rbtree_type * store)563 macro_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 */
572 static size_t
macro_length(char * text)573 macro_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 */
593 static int
do_buf_insert(char * buf,size_t remain,char * after,char * inserted)594 do_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 */
612 static char*
do_macro_recursion(rbtree_type * store,struct replay_runtime * runtime,char * at,size_t remain)613 do_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 */
629 static struct replay_var*
macro_getvar(rbtree_type * store,char * name)630 macro_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 */
639 static char*
do_macro_variable(rbtree_type * store,char * buf,size_t remain)640 do_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 */
669 static char*
do_macro_ctime(char * arg)670 do_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 */
688 static double
perform_arith(double x,char op,double y,double * res)689 perform_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 */
713 static char*
do_macro_arith(char * orig,size_t remain,char ** arithstart)714 do_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 */
761 static char*
do_macro_range(char * buf)762 do_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 
778 static char*
macro_expand(rbtree_type * store,struct replay_runtime * runtime,char ** text)779 macro_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 
851 char*
macro_process(rbtree_type * store,struct replay_runtime * runtime,char * text)852 macro_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 
879 char*
macro_lookup(rbtree_type * store,char * name)880 macro_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 
macro_print_debug(rbtree_type * store)887 void 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 
895 int
macro_assign(rbtree_type * store,char * name,char * value)896 macro_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 
testbound_selftest(void)923 void 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