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