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