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