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 if(parse_keyword(&remain, "FLUSH_MESSAGE")) {
352 mom->evt_type = repevt_flush_message;
353 while(isspace((unsigned char)*remain))
354 remain++;
355 strip_end_white(remain);
356 mom->string = strdup(remain);
357 if(!mom->string) fatal_exit("out of memory");
358 } else if(parse_keyword(&remain, "EXPIRE_MESSAGE")) {
359 mom->evt_type = repevt_expire_message;
360 while(isspace((unsigned char)*remain))
361 remain++;
362 strip_end_white(remain);
363 mom->string = strdup(remain);
364 if(!mom->string) fatal_exit("out of memory");
365 } else {
366 log_err("%d: unknown event type %s", pstate->lineno, remain);
367 free(mom);
368 return NULL;
369 }
370 while(isspace((unsigned char)*remain))
371 remain++;
372 if(parse_keyword(&remain, "ADDRESS")) {
373 while(isspace((unsigned char)*remain))
374 remain++;
375 strip_end_white(remain);
376 if(!extstrtoaddr(remain, &mom->addr, &mom->addrlen,
377 UNBOUND_DNS_PORT)) {
378 log_err("line %d: could not parse ADDRESS: %s",
379 pstate->lineno, remain);
380 free(mom);
381 return NULL;
382 }
383 }
384 if(parse_keyword(&remain, "ELAPSE")) {
385 double sec;
386 errno = 0;
387 sec = strtod(remain, &remain);
388 if(sec == 0. && errno != 0) {
389 log_err("line %d: could not parse ELAPSE: %s (%s)",
390 pstate->lineno, remain, strerror(errno));
391 free(mom);
392 return NULL;
393 }
394 #ifndef S_SPLINT_S
395 mom->elapse.tv_sec = (int)sec;
396 mom->elapse.tv_usec = (int)((sec - (double)mom->elapse.tv_sec)
397 *1000000. + 0.5);
398 #endif
399 }
400
401 if(readentry) {
402 mom->match = read_entry(in, name, pstate, 1);
403 if(!mom->match) {
404 free(mom);
405 return NULL;
406 }
407 }
408
409 return mom;
410 }
411
412 /** makes scenario with title on rest of line */
413 static struct replay_scenario*
make_scenario(char * line)414 make_scenario(char* line)
415 {
416 struct replay_scenario* scen;
417 while(isspace((unsigned char)*line))
418 line++;
419 if(!*line) {
420 log_err("scenario: no title given");
421 return NULL;
422 }
423 scen = (struct replay_scenario*)malloc(sizeof(struct replay_scenario));
424 if(!scen)
425 return NULL;
426 memset(scen, 0, sizeof(*scen));
427 scen->title = strdup(line);
428 if(!scen->title) {
429 free(scen);
430 return NULL;
431 }
432 return scen;
433 }
434
435 struct replay_scenario*
replay_scenario_read(FILE * in,const char * name,int * lineno)436 replay_scenario_read(FILE* in, const char* name, int* lineno)
437 {
438 char line[MAX_LINE_LEN];
439 char *parse;
440 struct replay_scenario* scen = NULL;
441 struct sldns_file_parse_state pstate;
442 line[MAX_LINE_LEN-1]=0;
443 memset(&pstate, 0, sizeof(pstate));
444 pstate.default_ttl = 3600;
445 pstate.lineno = *lineno;
446
447 while(fgets(line, MAX_LINE_LEN-1, in)) {
448 parse=line;
449 pstate.lineno++;
450 (*lineno)++;
451 while(isspace((unsigned char)*parse))
452 parse++;
453 if(!*parse)
454 continue; /* empty line */
455 if(parse_keyword(&parse, ";"))
456 continue; /* comment */
457 if(parse_keyword(&parse, "SCENARIO_BEGIN")) {
458 if(scen)
459 fatal_exit("%d: double SCENARIO_BEGIN", *lineno);
460 scen = make_scenario(parse);
461 if(!scen)
462 fatal_exit("%d: could not make scen", *lineno);
463 continue;
464 }
465 if(!scen)
466 fatal_exit("%d: expected SCENARIO", *lineno);
467 if(parse_keyword(&parse, "RANGE_BEGIN")) {
468 struct replay_range* newr = replay_range_read(parse,
469 in, name, &pstate, line);
470 if(!newr)
471 fatal_exit("%d: bad range", pstate.lineno);
472 *lineno = pstate.lineno;
473 newr->next_range = scen->range_list;
474 scen->range_list = newr;
475 } else if(parse_keyword(&parse, "STEP")) {
476 struct replay_moment* mom = replay_moment_read(parse,
477 in, name, &pstate);
478 if(!mom)
479 fatal_exit("%d: bad moment", pstate.lineno);
480 *lineno = pstate.lineno;
481 if(scen->mom_last &&
482 scen->mom_last->time_step >= mom->time_step)
483 fatal_exit("%d: time goes backwards", *lineno);
484 if(scen->mom_last)
485 scen->mom_last->mom_next = mom;
486 else scen->mom_first = mom;
487 scen->mom_last = mom;
488 } else if(parse_keyword(&parse, "SCENARIO_END")) {
489 struct replay_moment *p = scen->mom_first;
490 int num = 0;
491 while(p) {
492 num++;
493 p = p->mom_next;
494 }
495 log_info("Scenario has %d steps", num);
496 return scen;
497 }
498 }
499 log_err("scenario read failed at line %d (no SCENARIO_END?)", *lineno);
500 replay_scenario_delete(scen);
501 return NULL;
502 }
503
504 void
replay_scenario_delete(struct replay_scenario * scen)505 replay_scenario_delete(struct replay_scenario* scen)
506 {
507 struct replay_moment* mom, *momn;
508 struct replay_range* rng, *rngn;
509 if(!scen)
510 return;
511 free(scen->title);
512 mom = scen->mom_first;
513 while(mom) {
514 momn = mom->mom_next;
515 replay_moment_delete(mom);
516 mom = momn;
517 }
518 rng = scen->range_list;
519 while(rng) {
520 rngn = rng->next_range;
521 replay_range_delete(rng);
522 rng = rngn;
523 }
524 free(scen);
525 }
526
527 /** fetch oldest timer in list that is enabled */
528 static struct fake_timer*
first_timer(struct replay_runtime * runtime)529 first_timer(struct replay_runtime* runtime)
530 {
531 struct fake_timer* p, *res = NULL;
532 for(p=runtime->timer_list; p; p=p->next) {
533 if(!p->enabled)
534 continue;
535 if(!res)
536 res = p;
537 else if(timeval_smaller(&p->tv, &res->tv))
538 res = p;
539 }
540 return res;
541 }
542
543 struct fake_timer*
replay_get_oldest_timer(struct replay_runtime * runtime)544 replay_get_oldest_timer(struct replay_runtime* runtime)
545 {
546 struct fake_timer* t = first_timer(runtime);
547 if(t && timeval_smaller(&t->tv, &runtime->now_tv))
548 return t;
549 return NULL;
550 }
551
552 int
replay_var_compare(const void * a,const void * b)553 replay_var_compare(const void* a, const void* b)
554 {
555 struct replay_var* x = (struct replay_var*)a;
556 struct replay_var* y = (struct replay_var*)b;
557 return strcmp(x->name, y->name);
558 }
559
560 rbtree_type*
macro_store_create(void)561 macro_store_create(void)
562 {
563 return rbtree_create(&replay_var_compare);
564 }
565
566 /** helper function to delete macro values */
567 static void
del_macro(rbnode_type * x,void * ATTR_UNUSED (arg))568 del_macro(rbnode_type* x, void* ATTR_UNUSED(arg))
569 {
570 struct replay_var* v = (struct replay_var*)x;
571 free(v->name);
572 free(v->value);
573 free(v);
574 }
575
576 void
macro_store_delete(rbtree_type * store)577 macro_store_delete(rbtree_type* store)
578 {
579 if(!store)
580 return;
581 traverse_postorder(store, del_macro, NULL);
582 free(store);
583 }
584
585 /** return length of macro */
586 static size_t
macro_length(char * text)587 macro_length(char* text)
588 {
589 /* we are after ${, looking for } */
590 int depth = 0;
591 size_t len = 0;
592 while(*text) {
593 len++;
594 if(*text == '}') {
595 if(depth == 0)
596 break;
597 depth--;
598 } else if(text[0] == '$' && text[1] == '{') {
599 depth++;
600 }
601 text++;
602 }
603 return len;
604 }
605
606 /** insert new stuff at start of buffer */
607 static int
do_buf_insert(char * buf,size_t remain,char * after,char * inserted)608 do_buf_insert(char* buf, size_t remain, char* after, char* inserted)
609 {
610 char* save = strdup(after);
611 size_t len;
612 if(!save) return 0;
613 if(strlen(inserted) > remain) {
614 free(save);
615 return 0;
616 }
617 len = strlcpy(buf, inserted, remain);
618 buf += len;
619 remain -= len;
620 (void)strlcpy(buf, save, remain);
621 free(save);
622 return 1;
623 }
624
625 /** do macro recursion */
626 static char*
do_macro_recursion(rbtree_type * store,struct replay_runtime * runtime,char * at,size_t remain)627 do_macro_recursion(rbtree_type* store, struct replay_runtime* runtime,
628 char* at, size_t remain)
629 {
630 char* after = at+2;
631 char* expand = macro_expand(store, runtime, &after);
632 if(!expand)
633 return NULL; /* expansion failed */
634 if(!do_buf_insert(at, remain, after, expand)) {
635 free(expand);
636 return NULL;
637 }
638 free(expand);
639 return at; /* and parse over the expanded text to see if again */
640 }
641
642 /** get var from store */
643 static struct replay_var*
macro_getvar(rbtree_type * store,char * name)644 macro_getvar(rbtree_type* store, char* name)
645 {
646 struct replay_var k;
647 k.node.key = &k;
648 k.name = name;
649 return (struct replay_var*)rbtree_search(store, &k);
650 }
651
652 /** do macro variable */
653 static char*
do_macro_variable(rbtree_type * store,char * buf,size_t remain)654 do_macro_variable(rbtree_type* store, char* buf, size_t remain)
655 {
656 struct replay_var* v;
657 char* at = buf+1;
658 char* name = at;
659 char sv;
660 if(at[0]==0)
661 return NULL; /* no variable name after $ */
662 while(*at && (isalnum((unsigned char)*at) || *at=='_')) {
663 at++;
664 }
665 /* terminator, we are working in macro_expand() buffer */
666 sv = *at;
667 *at = 0;
668 v = macro_getvar(store, name);
669 *at = sv;
670
671 if(!v) {
672 log_err("variable is not defined: $%s", name);
673 return NULL; /* variable undefined is error for now */
674 }
675
676 /* insert the variable contents */
677 if(!do_buf_insert(buf, remain, at, v->value))
678 return NULL;
679 return buf; /* and expand the variable contents */
680 }
681
682 /** do ctime macro on argument */
683 static char*
do_macro_ctime(char * arg)684 do_macro_ctime(char* arg)
685 {
686 char buf[32];
687 time_t tt = (time_t)atoi(arg);
688 if(tt == 0 && strcmp(arg, "0") != 0) {
689 log_err("macro ctime: expected number, not: %s", arg);
690 return NULL;
691 }
692 ctime_r(&tt, buf);
693 #ifdef USE_WINSOCK
694 if(strlen(buf) > 10 && buf[7]==' ' && buf[8]=='0')
695 buf[8]=' '; /* fix error in windows ctime */
696 #endif
697 strip_end_white(buf);
698 return strdup(buf);
699 }
700
701 /** perform arithmetic operator */
702 static double
perform_arith(double x,char op,double y,double * res)703 perform_arith(double x, char op, double y, double* res)
704 {
705 switch(op) {
706 case '+':
707 *res = x+y;
708 break;
709 case '-':
710 *res = x-y;
711 break;
712 case '/':
713 *res = x/y;
714 break;
715 case '*':
716 *res = x*y;
717 break;
718 default:
719 *res = 0;
720 return 0;
721 }
722
723 return 1;
724 }
725
726 /** do macro arithmetic on two numbers and operand */
727 static char*
do_macro_arith(char * orig,size_t remain,char ** arithstart)728 do_macro_arith(char* orig, size_t remain, char** arithstart)
729 {
730 double x, y, result;
731 char operator;
732 int skip;
733 char buf[32];
734 char* at;
735 /* not yet done? we want number operand number expanded first. */
736 if(!*arithstart) {
737 /* remember start pos of expr, skip the first number */
738 at = orig;
739 *arithstart = at;
740 while(*at && (isdigit((unsigned char)*at) || *at == '.'))
741 at++;
742 return at;
743 }
744 /* move back to start */
745 remain += (size_t)(orig - *arithstart);
746 at = *arithstart;
747
748 /* parse operands */
749 if(sscanf(at, " %lf %c %lf%n", &x, &operator, &y, &skip) != 3) {
750 *arithstart = NULL;
751 return do_macro_arith(orig, remain, arithstart);
752 }
753 if(isdigit((unsigned char)operator)) {
754 *arithstart = orig;
755 return at+skip; /* do nothing, but setup for later number */
756 }
757
758 /* calculate result */
759 if(!perform_arith(x, operator, y, &result)) {
760 log_err("unknown operator: %s", at);
761 return NULL;
762 }
763
764 /* put result back in buffer */
765 snprintf(buf, sizeof(buf), "%.12g", result);
766 if(!do_buf_insert(at, remain, at+skip, buf))
767 return NULL;
768
769 /* the result can be part of another expression, restart that */
770 *arithstart = NULL;
771 return at;
772 }
773
774 /** Do range macro on expanded buffer */
775 static char*
do_macro_range(char * buf)776 do_macro_range(char* buf)
777 {
778 double x, y, z;
779 if(sscanf(buf, " %lf %lf %lf", &x, &y, &z) != 3) {
780 log_err("range func requires 3 args: %s", buf);
781 return NULL;
782 }
783 if(x <= y && y <= z) {
784 char res[1024];
785 snprintf(res, sizeof(res), "%.24g", y);
786 return strdup(res);
787 }
788 fatal_exit("value %.24g not in range [%.24g, %.24g]", y, x, z);
789 return NULL;
790 }
791
792 static char*
macro_expand(rbtree_type * store,struct replay_runtime * runtime,char ** text)793 macro_expand(rbtree_type* store, struct replay_runtime* runtime, char** text)
794 {
795 char buf[10240];
796 char* at = *text;
797 size_t len = macro_length(at);
798 int dofunc = 0;
799 char* arithstart = NULL;
800 if(len >= sizeof(buf))
801 return NULL; /* too long */
802 buf[0] = 0;
803 (void)strlcpy(buf, at, len+1-1); /* do not copy last '}' character */
804 at = buf;
805
806 /* check for functions */
807 if(strcmp(buf, "time") == 0) {
808 if(runtime)
809 snprintf(buf, sizeof(buf), ARG_LL "d", (long long)runtime->now_secs);
810 else
811 snprintf(buf, sizeof(buf), ARG_LL "d", (long long)0);
812 *text += len;
813 return strdup(buf);
814 } else if(strcmp(buf, "timeout") == 0) {
815 time_t res = 0;
816 if(runtime) {
817 struct fake_timer* t = first_timer(runtime);
818 if(t && (time_t)t->tv.tv_sec >= runtime->now_secs)
819 res = (time_t)t->tv.tv_sec - runtime->now_secs;
820 }
821 snprintf(buf, sizeof(buf), ARG_LL "d", (long long)res);
822 *text += len;
823 return strdup(buf);
824 } else if(strncmp(buf, "ctime ", 6) == 0 ||
825 strncmp(buf, "ctime\t", 6) == 0) {
826 at += 6;
827 dofunc = 1;
828 } else if(strncmp(buf, "range ", 6) == 0 ||
829 strncmp(buf, "range\t", 6) == 0) {
830 at += 6;
831 dofunc = 1;
832 }
833
834 /* actual macro text expansion */
835 while(*at) {
836 size_t remain = sizeof(buf)-strlen(buf);
837 if(strncmp(at, "${", 2) == 0) {
838 at = do_macro_recursion(store, runtime, at, remain);
839 } else if(*at == '$') {
840 at = do_macro_variable(store, at, remain);
841 } else if(isdigit((unsigned char)*at)) {
842 at = do_macro_arith(at, remain, &arithstart);
843 } else {
844 /* copy until whitespace or operator */
845 if(*at && (isalnum((unsigned char)*at) || *at=='_')) {
846 at++;
847 while(*at && (isalnum((unsigned char)*at) || *at=='_'))
848 at++;
849 } else at++;
850 }
851 if(!at) return NULL; /* failure */
852 }
853 *text += len;
854 if(dofunc) {
855 /* post process functions, buf has the argument(s) */
856 if(strncmp(buf, "ctime", 5) == 0) {
857 return do_macro_ctime(buf+6);
858 } else if(strncmp(buf, "range", 5) == 0) {
859 return do_macro_range(buf+6);
860 }
861 }
862 return strdup(buf);
863 }
864
865 char*
macro_process(rbtree_type * store,struct replay_runtime * runtime,char * text)866 macro_process(rbtree_type* store, struct replay_runtime* runtime, char* text)
867 {
868 char buf[10240];
869 char* next, *expand;
870 char* at = text;
871 if(!strstr(text, "${"))
872 return strdup(text); /* no macros */
873 buf[0] = 0;
874 buf[sizeof(buf)-1]=0;
875 while( (next=strstr(at, "${")) ) {
876 /* copy text before next macro */
877 if((size_t)(next-at) >= sizeof(buf)-strlen(buf))
878 return NULL; /* string too long */
879 (void)strlcpy(buf+strlen(buf), at, (size_t)(next-at+1));
880 /* process the macro itself */
881 next += 2;
882 expand = macro_expand(store, runtime, &next);
883 if(!expand) return NULL; /* expansion failed */
884 (void)strlcpy(buf+strlen(buf), expand, sizeof(buf)-strlen(buf));
885 free(expand);
886 at = next;
887 }
888 /* copy remainder fixed text */
889 (void)strlcpy(buf+strlen(buf), at, sizeof(buf)-strlen(buf));
890 return strdup(buf);
891 }
892
893 char*
macro_lookup(rbtree_type * store,char * name)894 macro_lookup(rbtree_type* store, char* name)
895 {
896 struct replay_var* x = macro_getvar(store, name);
897 if(!x) return strdup("");
898 return strdup(x->value);
899 }
900
macro_print_debug(rbtree_type * store)901 void macro_print_debug(rbtree_type* store)
902 {
903 struct replay_var* x;
904 RBTREE_FOR(x, struct replay_var*, store) {
905 log_info("%s = %s", x->name, x->value);
906 }
907 }
908
909 int
macro_assign(rbtree_type * store,char * name,char * value)910 macro_assign(rbtree_type* store, char* name, char* value)
911 {
912 struct replay_var* x = macro_getvar(store, name);
913 if(x) {
914 free(x->value);
915 } else {
916 x = (struct replay_var*)malloc(sizeof(*x));
917 if(!x) return 0;
918 x->node.key = x;
919 x->name = strdup(name);
920 if(!x->name) {
921 free(x);
922 return 0;
923 }
924 (void)rbtree_insert(store, &x->node);
925 }
926 x->value = strdup(value);
927 return x->value != NULL;
928 }
929
930 /* testbound assert function for selftest. counts the number of tests */
931 #define tb_assert(x) \
932 do { if(!(x)) fatal_exit("%s:%d: %s: assertion %s failed", \
933 __FILE__, __LINE__, __func__, #x); \
934 num_asserts++; \
935 } while(0);
936
testbound_selftest(void)937 void testbound_selftest(void)
938 {
939 /* test the macro store */
940 rbtree_type* store = macro_store_create();
941 char* v;
942 int r;
943 int num_asserts = 0;
944 tb_assert(store);
945
946 v = macro_lookup(store, "bla");
947 tb_assert(strcmp(v, "") == 0);
948 free(v);
949
950 v = macro_lookup(store, "vlerk");
951 tb_assert(strcmp(v, "") == 0);
952 free(v);
953
954 r = macro_assign(store, "bla", "waarde1");
955 tb_assert(r);
956
957 v = macro_lookup(store, "vlerk");
958 tb_assert(strcmp(v, "") == 0);
959 free(v);
960
961 v = macro_lookup(store, "bla");
962 tb_assert(strcmp(v, "waarde1") == 0);
963 free(v);
964
965 r = macro_assign(store, "vlerk", "kanteel");
966 tb_assert(r);
967
968 v = macro_lookup(store, "bla");
969 tb_assert(strcmp(v, "waarde1") == 0);
970 free(v);
971
972 v = macro_lookup(store, "vlerk");
973 tb_assert(strcmp(v, "kanteel") == 0);
974 free(v);
975
976 r = macro_assign(store, "bla", "ww");
977 tb_assert(r);
978
979 v = macro_lookup(store, "bla");
980 tb_assert(strcmp(v, "ww") == 0);
981 free(v);
982
983 tb_assert( macro_length("}") == 1);
984 tb_assert( macro_length("blabla}") == 7);
985 tb_assert( macro_length("bla${zoink}bla}") == 7+8);
986 tb_assert( macro_length("bla${zoink}${bla}bla}") == 7+8+6);
987
988 v = macro_process(store, NULL, "");
989 tb_assert( v && strcmp(v, "") == 0);
990 free(v);
991
992 v = macro_process(store, NULL, "${}");
993 tb_assert( v && strcmp(v, "") == 0);
994 free(v);
995
996 v = macro_process(store, NULL, "blabla ${} dinges");
997 tb_assert( v && strcmp(v, "blabla dinges") == 0);
998 free(v);
999
1000 v = macro_process(store, NULL, "1${$bla}2${$bla}3");
1001 tb_assert( v && strcmp(v, "1ww2ww3") == 0);
1002 free(v);
1003
1004 v = macro_process(store, NULL, "it is ${ctime 123456}");
1005 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0);
1006 free(v);
1007
1008 r = macro_assign(store, "t1", "123456");
1009 tb_assert(r);
1010 v = macro_process(store, NULL, "it is ${ctime ${$t1}}");
1011 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0);
1012 free(v);
1013
1014 v = macro_process(store, NULL, "it is ${ctime $t1}");
1015 tb_assert( v && strcmp(v, "it is Fri Jan 2 10:17:36 1970") == 0);
1016 free(v);
1017
1018 r = macro_assign(store, "x", "1");
1019 tb_assert(r);
1020 r = macro_assign(store, "y", "2");
1021 tb_assert(r);
1022 v = macro_process(store, NULL, "${$x + $x}");
1023 tb_assert( v && strcmp(v, "2") == 0);
1024 free(v);
1025 v = macro_process(store, NULL, "${$x - $x}");
1026 tb_assert( v && strcmp(v, "0") == 0);
1027 free(v);
1028 v = macro_process(store, NULL, "${$y * $y}");
1029 tb_assert( v && strcmp(v, "4") == 0);
1030 free(v);
1031 v = macro_process(store, NULL, "${32 / $y + $x + $y}");
1032 tb_assert( v && strcmp(v, "19") == 0);
1033 free(v);
1034
1035 v = macro_process(store, NULL, "${32 / ${$y+$y} + ${${100*3}/3}}");
1036 tb_assert( v && strcmp(v, "108") == 0);
1037 free(v);
1038
1039 v = macro_process(store, NULL, "${1 2 33 2 1}");
1040 tb_assert( v && strcmp(v, "1 2 33 2 1") == 0);
1041 free(v);
1042
1043 v = macro_process(store, NULL, "${123 3 + 5}");
1044 tb_assert( v && strcmp(v, "123 8") == 0);
1045 free(v);
1046
1047 v = macro_process(store, NULL, "${123 glug 3 + 5}");
1048 tb_assert( v && strcmp(v, "123 glug 8") == 0);
1049 free(v);
1050
1051 macro_store_delete(store);
1052 printf("selftest successful (%d checks).\n", num_asserts);
1053 }
1054