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